Lexicographically ordered storage (#454)
This commit is contained in:
commit
b8186bea2e
4
.gitignore
vendored
4
.gitignore
vendored
@ -54,6 +54,7 @@ src/distributed/token_sharing_rpc_messages.hpp
|
||||
src/distributed/updates_rpc_messages.hpp
|
||||
src/query/v2/frontend/ast/ast.hpp
|
||||
src/query/frontend/ast/ast.hpp
|
||||
src/storage/v3/bindings/ast/ast.hpp
|
||||
src/query/distributed/frontend/ast/ast_serialization.hpp
|
||||
src/query/v2/distributed/frontend/ast/ast_serialization.hpp
|
||||
src/durability/distributed/state_delta.hpp
|
||||
@ -61,12 +62,15 @@ src/durability/single_node/state_delta.hpp
|
||||
src/durability/single_node_ha/state_delta.hpp
|
||||
src/query/frontend/semantic/symbol.hpp
|
||||
src/query/v2/frontend/semantic/symbol.hpp
|
||||
src/expr/semantic/symbol.hpp
|
||||
src/query/distributed/frontend/semantic/symbol_serialization.hpp
|
||||
src/query/v2/distributed/frontend/semantic/symbol_serialization.hpp
|
||||
src/query/distributed/plan/ops.hpp
|
||||
src/query/v2/distributed/plan/ops.hpp
|
||||
src/query/plan/operator.hpp
|
||||
src/query/v2/plan/operator.hpp
|
||||
src/parser/opencypher/generated
|
||||
src/expr/semantic/symbol.hpp
|
||||
src/raft/log_entry.hpp
|
||||
src/raft/raft_rpc_messages.hpp
|
||||
src/raft/snapshot_metadata.hpp
|
||||
|
@ -6,4 +6,4 @@ find_package(fmt REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(mg-coordinator STATIC ${coordinator_src_files})
|
||||
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils)
|
||||
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils mg-storage-v3)
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
|
||||
#include "io/time.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
@ -22,7 +24,13 @@ struct Hlc {
|
||||
uint64_t logical_id;
|
||||
Time coordinator_wall_clock;
|
||||
|
||||
auto operator<=>(const Hlc &other) const { return logical_id <=> other.logical_id; }
|
||||
|
||||
bool operator==(const Hlc &other) const = default;
|
||||
bool operator<(const Hlc &other) const = default;
|
||||
bool operator==(const uint64_t other) const { return logical_id == other; }
|
||||
bool operator<(const uint64_t other) const { return logical_id < other; }
|
||||
bool operator>=(const uint64_t other) const { return logical_id >= other; }
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
@ -32,4 +32,21 @@ class ExpressionRuntimeException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
class RedeclareVariableError : public SemanticException {
|
||||
public:
|
||||
explicit RedeclareVariableError(const std::string &name) : SemanticException("Redeclaring variable: " + name + ".") {}
|
||||
};
|
||||
|
||||
class UnboundVariableError : public SemanticException {
|
||||
public:
|
||||
explicit UnboundVariableError(const std::string &name) : SemanticException("Unbound variable: " + name + ".") {}
|
||||
};
|
||||
|
||||
class TypeMismatchError : public SemanticException {
|
||||
public:
|
||||
TypeMismatchError(const std::string &name, const std::string &datum, const std::string &expected)
|
||||
: SemanticException(fmt::format("Type mismatch: {} already defined as {}, expected {}.", name, datum, expected)) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::expr
|
||||
|
@ -28,7 +28,7 @@
|
||||
namespace memgraph::expr {
|
||||
|
||||
template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId,
|
||||
typename PropertyValue, typename ConvFunction, typename Error>
|
||||
typename PropertyValue, typename ConvFunctor, typename Error>
|
||||
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
public:
|
||||
ExpressionEvaluator(Frame<TypedValue> *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx,
|
||||
@ -719,7 +719,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
}
|
||||
}
|
||||
return conv_(*maybe_prop);
|
||||
return conv_(*maybe_prop, ctx_->memory);
|
||||
}
|
||||
|
||||
template <class TRecordAccessor>
|
||||
@ -746,7 +746,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
}
|
||||
}
|
||||
return conv_(*maybe_prop);
|
||||
return conv_(*maybe_prop, ctx_->memory);
|
||||
}
|
||||
|
||||
LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
|
||||
@ -757,7 +757,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
DbAccessor *dba_;
|
||||
// which switching approach should be used when evaluating
|
||||
StorageView view_;
|
||||
ConvFunction conv_;
|
||||
ConvFunctor conv_;
|
||||
};
|
||||
|
||||
/// A helper function for evaluating an expression that's an int.
|
||||
|
@ -116,9 +116,8 @@ class TypedValueT {
|
||||
return hash;
|
||||
}
|
||||
case TypedValueT::Type::Vertex:
|
||||
return value.ValueVertex().Gid().AsUint();
|
||||
case TypedValueT::Type::Edge:
|
||||
return value.ValueEdge().Gid().AsUint();
|
||||
return 0;
|
||||
case TypedValueT::Type::Path: {
|
||||
const auto &vertices = value.ValuePath().vertices();
|
||||
const auto &edges = value.ValuePath().edges();
|
||||
|
@ -16,7 +16,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
@ -64,16 +64,16 @@ query::v2::TypedValue ToTypedValue(const Value &value) {
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const query::v2::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
return ToBoltVertex(vertex.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const query::v2::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
return ToBoltEdge(edge.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Storage &db,
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Shard &db,
|
||||
storage::v3::View view) {
|
||||
switch (value.type()) {
|
||||
case query::v2::TypedValue::Type::Null:
|
||||
@ -132,8 +132,9 @@ storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(vertex.Gid().AsUint());
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
auto maybe_labels = vertex.Labels(view);
|
||||
if (maybe_labels.HasError()) return maybe_labels.GetError();
|
||||
std::vector<std::string> labels;
|
||||
@ -151,10 +152,11 @@ storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3:
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(edge.Gid().AsUint());
|
||||
auto from = communication::bolt::Id::FromUint(edge.FromVertex().Gid().AsUint());
|
||||
auto to = communication::bolt::Id::FromUint(edge.ToVertex().Gid().AsUint());
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
auto from = communication::bolt::Id::FromUint(0);
|
||||
auto to = communication::bolt::Id::FromUint(0);
|
||||
const auto &type = db.EdgeTypeToName(edge.EdgeType());
|
||||
auto maybe_properties = edge.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
@ -165,7 +167,7 @@ storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::Edg
|
||||
return communication::bolt::Edge{id, from, to, type, properties};
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db,
|
||||
storage::v3::View view) {
|
||||
std::vector<communication::bolt::Vertex> vertices;
|
||||
vertices.reserve(path.vertices().size());
|
||||
|
@ -28,36 +28,36 @@ namespace memgraph::glue::v2 {
|
||||
|
||||
/// @param storage::v3::VertexAccessor for converting to
|
||||
/// communication::bolt::Vertex.
|
||||
/// @param storage::v3::Storage for getting label and property names.
|
||||
/// @param storage::v3::Shard for getting label and property names.
|
||||
/// @param storage::v3::View for deciding which vertex attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
/// @param storage::v3::EdgeAccessor for converting to communication::bolt::Edge.
|
||||
/// @param storage::v3::Storage for getting edge type and property names.
|
||||
/// @param storage::v3::Shard for getting edge type and property names.
|
||||
/// @param storage::v3::View for deciding which edge attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
/// @param query::v2::Path for converting to communication::bolt::Path.
|
||||
/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db,
|
||||
storage::v3::View view);
|
||||
|
||||
/// @param query::v2::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
query::v2::TypedValue ToTypedValue(const communication::bolt::Value &value);
|
||||
|
||||
|
@ -55,15 +55,17 @@ class SimulatorHandle {
|
||||
|
||||
void TimeoutPromisesPastDeadline() {
|
||||
const Time now = cluster_wide_time_microseconds_;
|
||||
|
||||
for (auto &[promise_key, dop] : promises_) {
|
||||
for (auto it = promises_.begin(); it != promises_.end();) {
|
||||
auto &[promise_key, dop] = *it;
|
||||
if (dop.deadline < now) {
|
||||
spdlog::debug("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(),
|
||||
promise_key.replier_address.ToString());
|
||||
spdlog::info("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(),
|
||||
promise_key.replier_address.ToString());
|
||||
std::move(dop).promise.TimeOut();
|
||||
promises_.erase(promise_key);
|
||||
it = promises_.erase(it);
|
||||
|
||||
stats_.timed_out_requests++;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,18 +17,24 @@
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_store.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
inline const auto lam = [](const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); };
|
||||
struct PropertyToTypedValueConverter {
|
||||
TypedValue operator()(const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); }
|
||||
|
||||
TypedValue operator()(const auto &val, utils::MemoryResource *mem) {
|
||||
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val, mem);
|
||||
}
|
||||
};
|
||||
|
||||
using ExpressionEvaluator =
|
||||
memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View,
|
||||
storage::v3::LabelId, storage::v3::PropertyStore, decltype(lam),
|
||||
storage::v3::LabelId, storage::v3::PropertyStore, PropertyToTypedValueConverter,
|
||||
memgraph::storage::v3::Error>;
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -88,6 +88,18 @@ concept AccessorWithSetProperty = requires(T accessor, const storage::v3::Proper
|
||||
{ accessor.SetProperty(key, new_value) } -> std::same_as<storage::v3::Result<storage::v3::PropertyValue>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept AccessorWithSetPropertyAndValidate = requires(T accessor, const storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue new_value) {
|
||||
{
|
||||
accessor.SetPropertyAndValidate(key, new_value)
|
||||
} -> std::same_as<storage::v3::ResultSchema<storage::v3::PropertyValue>>;
|
||||
};
|
||||
|
||||
template <typename TRecordAccessor>
|
||||
concept RecordAccessor =
|
||||
AccessorWithSetProperty<TRecordAccessor> || AccessorWithSetPropertyAndValidate<TRecordAccessor>;
|
||||
|
||||
inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_violation, const DbAccessor &dba) {
|
||||
switch (schema_violation.status) {
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY: {
|
||||
@ -111,13 +123,14 @@ inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_vio
|
||||
*schema_violation.violated_property_value,
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL: {
|
||||
throw SchemaViolationException(fmt::format("Cannot add or remove label :{} since it is a primary label",
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL: {
|
||||
throw SchemaViolationException(fmt::format(
|
||||
"Adding primary label as secondary or removing primary label:", *schema_violation.violated_property_value,
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Cannot create vertex with secondary label :{}", dba.LabelToName(schema_violation.label)));
|
||||
throw SchemaViolationException(fmt::format("Cannot create vertex where primary label is secondary:{}",
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,7 +152,7 @@ inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) {
|
||||
/// Set a property `value` mapped with given `key` on a `record`.
|
||||
///
|
||||
/// @throw QueryRuntimeException if value cannot be set as a property value
|
||||
template <AccessorWithSetProperty T>
|
||||
template <RecordAccessor T>
|
||||
storage::v3::PropertyValue PropsSetChecked(T *record, const DbAccessor &dba, const storage::v3::PropertyId &key,
|
||||
const TypedValue &value) {
|
||||
try {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
@ -19,6 +20,7 @@
|
||||
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
|
||||
@ -109,13 +111,17 @@ class VertexAccessor final {
|
||||
|
||||
auto PrimaryLabel(storage::v3::View view) const { return impl_.PrimaryLabel(view); }
|
||||
|
||||
storage::v3::Result<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabel(label); }
|
||||
auto PrimaryKey(storage::v3::View view) const { return impl_.PrimaryKey(view); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabelAndValidate(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.AddLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::Result<bool> RemoveLabel(storage::v3::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
storage::v3::ResultSchema<bool> RemoveLabel(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<bool> RemoveLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
@ -132,9 +138,9 @@ class VertexAccessor final {
|
||||
return impl_.GetProperty(key, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetPropertyAndValidate(key, value);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetPropertyAndValidate(
|
||||
@ -162,7 +168,8 @@ class VertexAccessor final {
|
||||
auto InEdges(storage::v3::View view, const std::vector<storage::v3::EdgeTypeId> &edge_types,
|
||||
const VertexAccessor &dest) const
|
||||
-> storage::v3::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
||||
auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
|
||||
const auto dest_id = dest.impl_.Id(view).GetValue();
|
||||
auto maybe_edges = impl_.InEdges(view, edge_types, &dest_id);
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
}
|
||||
@ -179,7 +186,8 @@ class VertexAccessor final {
|
||||
auto OutEdges(storage::v3::View view, const std::vector<storage::v3::EdgeTypeId> &edge_types,
|
||||
const VertexAccessor &dest) const
|
||||
-> storage::v3::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
||||
auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
|
||||
const auto dest_id = dest.impl_.Id(view).GetValue();
|
||||
auto maybe_edges = impl_.OutEdges(view, edge_types, &dest_id);
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
}
|
||||
@ -188,9 +196,8 @@ class VertexAccessor final {
|
||||
|
||||
storage::v3::Result<size_t> OutDegree(storage::v3::View view) const { return impl_.OutDegree(view); }
|
||||
|
||||
int64_t CypherId() const { return impl_.Gid().AsInt(); }
|
||||
|
||||
storage::v3::Gid Gid() const noexcept { return impl_.Gid(); }
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
static int64_t CypherId() { return 1; }
|
||||
|
||||
bool operator==(const VertexAccessor &v) const noexcept {
|
||||
static_assert(noexcept(impl_ == v.impl_));
|
||||
@ -200,14 +207,19 @@ class VertexAccessor final {
|
||||
bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); }
|
||||
};
|
||||
|
||||
inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnull-dereference"
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker)
|
||||
inline VertexAccessor EdgeAccessor::To() const { return *static_cast<VertexAccessor *>(nullptr); }
|
||||
|
||||
inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker)
|
||||
inline VertexAccessor EdgeAccessor::From() const { return *static_cast<VertexAccessor *>(nullptr); }
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class DbAccessor final {
|
||||
storage::v3::Storage::Accessor *accessor_;
|
||||
storage::v3::Shard::Accessor *accessor_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::v3::VerticesIterable iterable_;
|
||||
@ -239,16 +251,18 @@ class DbAccessor final {
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::v3::Storage::Accessor *accessor) : accessor_(accessor) {}
|
||||
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::Gid gid, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(gid, view);
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
std::optional<VertexAccessor> FindVertex(uint64_t /*unused*/) { return std::nullopt; }
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(primary_key, view);
|
||||
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void FinalizeTransaction() { accessor_->FinalizeTransaction(); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
|
||||
@ -270,9 +284,6 @@ class DbAccessor final {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
// TODO Remove when query modules have been fixed
|
||||
[[deprecated]] VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); }
|
||||
|
||||
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
|
||||
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
|
||||
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
|
||||
@ -285,13 +296,15 @@ class DbAccessor final {
|
||||
|
||||
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::v3::EdgeTypeId &edge_type) {
|
||||
auto maybe_edge = accessor_->CreateEdge(&from->impl_, &to->impl_, edge_type);
|
||||
static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0);
|
||||
auto maybe_edge = accessor_->CreateEdge(from->impl_.Id(storage::v3::View::NEW).GetValue(),
|
||||
to->impl_.Id(storage::v3::View::NEW).GetValue(), edge_type, kDummyGid);
|
||||
if (maybe_edge.HasError()) return storage::v3::Result<EdgeAccessor>(maybe_edge.GetError());
|
||||
return EdgeAccessor(*maybe_edge);
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
||||
auto res = accessor_->DeleteEdge(&edge->impl_);
|
||||
auto res = accessor_->DeleteEdge(edge->impl_.FromVertex(), edge->impl_.ToVertex(), edge->impl_.Gid());
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
@ -342,10 +355,15 @@ class DbAccessor final {
|
||||
return {std::make_optional<VertexAccessor>(*value)};
|
||||
}
|
||||
|
||||
// TODO(jbajic) Query engine should have a map of labels, properties and edge
|
||||
// types
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }
|
||||
|
||||
const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); }
|
||||
@ -356,7 +374,7 @@ class DbAccessor final {
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
utils::BasicResult<storage::v3::ConstraintViolation, void> Commit() { return accessor_->Commit(); }
|
||||
void Commit() { return accessor_->Commit(coordinator::Hlc{}); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
@ -387,8 +405,6 @@ class DbAccessor final {
|
||||
|
||||
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
|
||||
|
||||
storage::v3::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
|
||||
|
||||
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
|
||||
|
||||
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
|
||||
|
@ -269,10 +269,6 @@ PullPlanDump::PullPlanDump(DbAccessor *dba)
|
||||
CreateLabelIndicesPullChunk(),
|
||||
// Dump all label property indices
|
||||
CreateLabelPropertyIndicesPullChunk(),
|
||||
// Dump all existence constraints
|
||||
CreateExistenceConstraintsPullChunk(),
|
||||
// Dump all unique constraints
|
||||
CreateUniqueConstraintsPullChunk(),
|
||||
// Create internal index for faster edge creation
|
||||
CreateInternalIndexPullChunk(),
|
||||
// Dump all vertices
|
||||
@ -364,60 +360,6 @@ PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() {
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateExistenceConstraintsPullChunk() {
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of constraint vectors
|
||||
if (!constraints_info_) {
|
||||
constraints_info_.emplace(dba_->ListAllConstraints());
|
||||
}
|
||||
|
||||
const auto &existence = constraints_info_->existence;
|
||||
size_t local_counter = 0;
|
||||
while (global_index < existence.size() && (!n || local_counter < *n)) {
|
||||
const auto &constraint = existence[global_index];
|
||||
std::ostringstream os;
|
||||
DumpExistenceConstraint(&os, dba_, constraint.first, constraint.second);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == existence.size()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateUniqueConstraintsPullChunk() {
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of constraint vectors
|
||||
if (!constraints_info_) {
|
||||
constraints_info_.emplace(dba_->ListAllConstraints());
|
||||
}
|
||||
|
||||
const auto &unique = constraints_info_->unique;
|
||||
size_t local_counter = 0;
|
||||
while (global_index < unique.size() && (!n || local_counter < *n)) {
|
||||
const auto &constraint = unique[global_index];
|
||||
std::ostringstream os;
|
||||
DumpUniqueConstraint(&os, dba_, constraint.first, constraint.second);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == unique.size()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexPullChunk() {
|
||||
return [this](AnyStream *stream, std::optional<int>) mutable -> std::optional<size_t> {
|
||||
if (vertices_iterable_.begin() != vertices_iterable_.end()) {
|
||||
|
@ -32,7 +32,6 @@ struct PullPlanDump {
|
||||
query::v2::DbAccessor *dba_ = nullptr;
|
||||
|
||||
std::optional<storage::v3::IndicesInfo> indices_info_ = std::nullopt;
|
||||
std::optional<storage::v3::ConstraintsInfo> constraints_info_ = std::nullopt;
|
||||
|
||||
using VertexAccessorIterable = decltype(std::declval<query::v2::DbAccessor>().Vertices(storage::v3::View::OLD));
|
||||
using VertexAccessorIterableIterator = decltype(std::declval<VertexAccessorIterable>().begin());
|
||||
@ -55,8 +54,6 @@ struct PullPlanDump {
|
||||
|
||||
PullChunk CreateLabelIndicesPullChunk();
|
||||
PullChunk CreateLabelPropertyIndicesPullChunk();
|
||||
PullChunk CreateExistenceConstraintsPullChunk();
|
||||
PullChunk CreateUniqueConstraintsPullChunk();
|
||||
PullChunk CreateInternalIndexPullChunk();
|
||||
PullChunk CreateVertexPullChunk();
|
||||
PullChunk CreateEdgePullChunk();
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "query/v2/stream/common.hpp"
|
||||
#include "query/v2/trigger.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/csv_parsing.hpp"
|
||||
@ -128,135 +129,25 @@ std::optional<std::string> GetOptionalStringValue(query::v2::Expression *express
|
||||
|
||||
class ReplQueryHandler final : public query::v2::ReplicationQueryHandler {
|
||||
public:
|
||||
explicit ReplQueryHandler(storage::v3::Storage *db) : db_(db) {}
|
||||
explicit ReplQueryHandler(storage::v3::Shard * /*db*/) {}
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override {
|
||||
if (replication_role == ReplicationQuery::ReplicationRole::MAIN) {
|
||||
if (!db_->SetMainReplicationRole()) {
|
||||
throw QueryRuntimeException("Couldn't set role to main!");
|
||||
}
|
||||
}
|
||||
if (replication_role == ReplicationQuery::ReplicationRole::REPLICA) {
|
||||
if (!port || *port < 0 || *port > std::numeric_limits<uint16_t>::max()) {
|
||||
throw QueryRuntimeException("Port number invalid!");
|
||||
}
|
||||
if (!db_->SetReplicaRole(
|
||||
io::network::Endpoint(query::v2::kDefaultReplicationServerIp, static_cast<uint16_t>(*port)))) {
|
||||
throw QueryRuntimeException("Couldn't set role to replica!");
|
||||
}
|
||||
}
|
||||
}
|
||||
void SetReplicationRole(ReplicationQuery::ReplicationRole /*replication_role*/,
|
||||
std::optional<int64_t> /*port*/) override {}
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
ReplicationQuery::ReplicationRole ShowReplicationRole() const override {
|
||||
switch (db_->GetReplicationRole()) {
|
||||
case storage::v3::ReplicationRole::MAIN:
|
||||
return ReplicationQuery::ReplicationRole::MAIN;
|
||||
case storage::v3::ReplicationRole::REPLICA:
|
||||
return ReplicationQuery::ReplicationRole::REPLICA;
|
||||
}
|
||||
throw QueryRuntimeException("Couldn't show replication role - invalid role set!");
|
||||
}
|
||||
ReplicationQuery::ReplicationRole ShowReplicationRole() const override { return {}; }
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void RegisterReplica(const std::string &name, const std::string &socket_address,
|
||||
const ReplicationQuery::SyncMode sync_mode, const std::optional<double> timeout,
|
||||
const std::chrono::seconds replica_check_frequency) override {
|
||||
if (db_->GetReplicationRole() == storage::v3::ReplicationRole::REPLICA) {
|
||||
// replica can't register another replica
|
||||
throw QueryRuntimeException("Replica can't register another replica!");
|
||||
}
|
||||
|
||||
storage::v3::replication::ReplicationMode repl_mode;
|
||||
switch (sync_mode) {
|
||||
case ReplicationQuery::SyncMode::ASYNC: {
|
||||
repl_mode = storage::v3::replication::ReplicationMode::ASYNC;
|
||||
break;
|
||||
}
|
||||
case ReplicationQuery::SyncMode::SYNC: {
|
||||
repl_mode = storage::v3::replication::ReplicationMode::SYNC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto maybe_ip_and_port =
|
||||
io::network::Endpoint::ParseSocketOrIpAddress(socket_address, query::v2::kDefaultReplicationPort);
|
||||
if (maybe_ip_and_port) {
|
||||
auto [ip, port] = *maybe_ip_and_port;
|
||||
auto ret = db_->RegisterReplica(
|
||||
name, {std::move(ip), port}, repl_mode,
|
||||
{.timeout = timeout, .replica_check_frequency = replica_check_frequency, .ssl = std::nullopt});
|
||||
if (ret.HasError()) {
|
||||
throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name));
|
||||
}
|
||||
} else {
|
||||
throw QueryRuntimeException("Invalid socket address!");
|
||||
}
|
||||
}
|
||||
void RegisterReplica(const std::string & /*name*/, const std::string & /*socket_address*/,
|
||||
const ReplicationQuery::SyncMode /*sync_mode*/, const std::optional<double> /*timeout*/,
|
||||
const std::chrono::seconds /*replica_check_frequency*/) override {}
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void DropReplica(const std::string &replica_name) override {
|
||||
if (db_->GetReplicationRole() == storage::v3::ReplicationRole::REPLICA) {
|
||||
// replica can't unregister a replica
|
||||
throw QueryRuntimeException("Replica can't unregister a replica!");
|
||||
}
|
||||
if (!db_->UnregisterReplica(replica_name)) {
|
||||
throw QueryRuntimeException(fmt::format("Couldn't unregister the replica '{}'", replica_name));
|
||||
}
|
||||
}
|
||||
void DropReplica(const std::string & /*replica_name*/) override {}
|
||||
|
||||
using Replica = ReplicationQueryHandler::Replica;
|
||||
std::vector<Replica> ShowReplicas() const override {
|
||||
if (db_->GetReplicationRole() == storage::v3::ReplicationRole::REPLICA) {
|
||||
// replica can't show registered replicas (it shouldn't have any)
|
||||
throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!");
|
||||
}
|
||||
|
||||
auto repl_infos = db_->ReplicasInfo();
|
||||
std::vector<Replica> replicas;
|
||||
replicas.reserve(repl_infos.size());
|
||||
|
||||
const auto from_info = [](const auto &repl_info) -> Replica {
|
||||
Replica replica;
|
||||
replica.name = repl_info.name;
|
||||
replica.socket_address = repl_info.endpoint.SocketAddress();
|
||||
switch (repl_info.mode) {
|
||||
case storage::v3::replication::ReplicationMode::SYNC:
|
||||
replica.sync_mode = ReplicationQuery::SyncMode::SYNC;
|
||||
break;
|
||||
case storage::v3::replication::ReplicationMode::ASYNC:
|
||||
replica.sync_mode = ReplicationQuery::SyncMode::ASYNC;
|
||||
break;
|
||||
}
|
||||
if (repl_info.timeout) {
|
||||
replica.timeout = *repl_info.timeout;
|
||||
}
|
||||
|
||||
switch (repl_info.state) {
|
||||
case storage::v3::replication::ReplicaState::READY:
|
||||
replica.state = ReplicationQuery::ReplicaState::READY;
|
||||
break;
|
||||
case storage::v3::replication::ReplicaState::REPLICATING:
|
||||
replica.state = ReplicationQuery::ReplicaState::REPLICATING;
|
||||
break;
|
||||
case storage::v3::replication::ReplicaState::RECOVERY:
|
||||
replica.state = ReplicationQuery::ReplicaState::RECOVERY;
|
||||
break;
|
||||
case storage::v3::replication::ReplicaState::INVALID:
|
||||
replica.state = ReplicationQuery::ReplicaState::INVALID;
|
||||
break;
|
||||
}
|
||||
|
||||
return replica;
|
||||
};
|
||||
|
||||
std::transform(repl_infos.begin(), repl_infos.end(), std::back_inserter(replicas), from_info);
|
||||
return replicas;
|
||||
}
|
||||
|
||||
private:
|
||||
storage::v3::Storage *db_;
|
||||
std::vector<Replica> ShowReplicas() const override { return {}; }
|
||||
};
|
||||
/// returns false if the replication role can't be set
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
@ -914,7 +805,7 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp
|
||||
callback.header = {"property_name", "property_type"};
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
const auto label = interpreter_context->NameToLabelId(primary_label.name);
|
||||
const auto *schema = db->GetSchema(label);
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
if (schema) {
|
||||
@ -939,11 +830,11 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_,
|
||||
schema_type_map = std::move(schema_type_map)]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
const auto label = interpreter_context->NameToLabelId(primary_label.name);
|
||||
std::vector<storage::v3::SchemaProperty> schemas_types;
|
||||
schemas_types.reserve(schema_type_map.size());
|
||||
for (const auto &schema_type : schema_type_map) {
|
||||
auto property_id = db->NameToProperty(schema_type.first.name);
|
||||
auto property_id = interpreter_context->NameToPropertyId(schema_type.first.name);
|
||||
schemas_types.push_back({property_id, schema_type.second});
|
||||
}
|
||||
if (!db->CreateSchema(label, schemas_types)) {
|
||||
@ -958,8 +849,7 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp
|
||||
case SchemaQuery::Action::DROP_SCHEMA: {
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
|
||||
const auto label = interpreter_context->NameToLabelId(primary_label.name);
|
||||
if (!db->DropSchema(label)) {
|
||||
throw QueryException(fmt::format("Schema on label :{} does not exist!", primary_label.name));
|
||||
}
|
||||
@ -1139,7 +1029,7 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
|
||||
using RWType = plan::ReadWriteTypeChecker::RWType;
|
||||
} // namespace
|
||||
|
||||
InterpreterContext::InterpreterContext(storage::v3::Storage *db, const InterpreterConfig config,
|
||||
InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory)
|
||||
: db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory / "streams"} {}
|
||||
|
||||
@ -1158,8 +1048,8 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
in_explicit_transaction_ = true;
|
||||
expect_rollback_ = false;
|
||||
|
||||
db_accessor_ = std::make_unique<storage::v3::Storage::Accessor>(
|
||||
interpreter_context_->db->Access(GetIsolationLevelOverride()));
|
||||
db_accessor_ = std::make_unique<storage::v3::Shard::Accessor>(
|
||||
interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride()));
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
|
||||
if (interpreter_context_->trigger_store.HasTriggers()) {
|
||||
@ -1430,14 +1320,14 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
}
|
||||
};
|
||||
|
||||
auto label = interpreter_context->db->NameToLabel(index_query->label_.name);
|
||||
const auto label = interpreter_context->NameToLabelId(index_query->label_.name);
|
||||
|
||||
std::vector<storage::v3::PropertyId> properties;
|
||||
std::vector<std::string> properties_string;
|
||||
properties.reserve(index_query->properties_.size());
|
||||
properties_string.reserve(index_query->properties_.size());
|
||||
for (const auto &prop : index_query->properties_) {
|
||||
properties.push_back(interpreter_context->db->NameToProperty(prop.name));
|
||||
properties.push_back(interpreter_context->NameToPropertyId(prop.name));
|
||||
properties_string.push_back(prop.name);
|
||||
}
|
||||
auto properties_stringified = utils::Join(properties_string, ", ");
|
||||
@ -1581,48 +1471,22 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, const bool in_ex
|
||||
|
||||
PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba) {
|
||||
if (in_explicit_transaction) {
|
||||
throw LockPathModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *lock_path_query = utils::Downcast<LockPathQuery>(parsed_query.query);
|
||||
|
||||
return PreparedQuery{{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[interpreter_context, action = lock_path_query->action_](
|
||||
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
switch (action) {
|
||||
case LockPathQuery::Action::LOCK_PATH:
|
||||
if (!interpreter_context->db->LockPath()) {
|
||||
throw QueryRuntimeException("Failed to lock the data directory");
|
||||
}
|
||||
break;
|
||||
case LockPathQuery::Action::UNLOCK_PATH:
|
||||
if (!interpreter_context->db->UnlockPath()) {
|
||||
throw QueryRuntimeException("Failed to unlock the data directory");
|
||||
}
|
||||
break;
|
||||
}
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
throw SemanticException("LockPath query is not supported!");
|
||||
}
|
||||
|
||||
PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context) {
|
||||
InterpreterContext * /*interpreter_context*/) {
|
||||
if (in_explicit_transaction) {
|
||||
throw FreeMemoryModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
return PreparedQuery{
|
||||
{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[interpreter_context](AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
interpreter_context->db->FreeMemory();
|
||||
memory::PurgeUnusedMemory();
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
return PreparedQuery{{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[](AnyStream * /*stream*/, std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> {
|
||||
memory::PurgeUnusedMemory();
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
TriggerEventType ToTriggerEventType(const TriggerQuery::EventType event_type) {
|
||||
@ -1835,24 +1699,7 @@ PreparedQuery PrepareIsolationLevelQuery(ParsedQuery parsed_query, const bool in
|
||||
|
||||
PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context) {
|
||||
if (in_explicit_transaction) {
|
||||
throw CreateSnapshotInMulticommandTxException();
|
||||
}
|
||||
|
||||
return PreparedQuery{
|
||||
{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[interpreter_context](AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
if (auto maybe_error = interpreter_context->db->CreateSnapshot(); maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
case storage::v3::Storage::CreateSnapshotError::DisabledForReplica:
|
||||
throw utils::BasicException(
|
||||
"Failed to create a snapshot. Replica instances are not allowed to create them.");
|
||||
}
|
||||
}
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
throw SemanticException("CreateSnapshot query is not supported!");
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSettingQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, DbAccessor *dba) {
|
||||
@ -1900,8 +1747,8 @@ PreparedQuery PrepareVersionQuery(ParsedQuery parsed_query, const bool in_explic
|
||||
}
|
||||
|
||||
PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
storage::v3::Storage *db, utils::MemoryResource *execution_memory) {
|
||||
std::map<std::string, TypedValue> * /*summary*/, InterpreterContext *interpreter_context,
|
||||
storage::v3::Shard *db, utils::MemoryResource * /*execution_memory*/) {
|
||||
if (in_explicit_transaction) {
|
||||
throw InfoInMulticommandTxException();
|
||||
}
|
||||
@ -1920,7 +1767,6 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
{TypedValue("edge_count"), TypedValue(static_cast<int64_t>(info.edge_count))},
|
||||
{TypedValue("average_degree"), TypedValue(info.average_degree)},
|
||||
{TypedValue("memory_usage"), TypedValue(static_cast<int64_t>(info.memory_usage))},
|
||||
{TypedValue("disk_usage"), TypedValue(static_cast<int64_t>(info.disk_usage))},
|
||||
{TypedValue("memory_allocated"), TypedValue(static_cast<int64_t>(utils::total_memory_tracker.Amount()))},
|
||||
{TypedValue("allocation_limit"),
|
||||
TypedValue(static_cast<int64_t>(utils::total_memory_tracker.HardLimit()))}};
|
||||
@ -1945,28 +1791,7 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
};
|
||||
break;
|
||||
case InfoQuery::InfoType::CONSTRAINT:
|
||||
header = {"constraint type", "label", "properties"};
|
||||
handler = [interpreter_context] {
|
||||
auto *db = interpreter_context->db;
|
||||
auto info = db->ListAllConstraints();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(info.existence.size() + info.unique.size());
|
||||
for (const auto &item : info.existence) {
|
||||
results.push_back({TypedValue("exists"), TypedValue(db->LabelToName(item.first)),
|
||||
TypedValue(db->PropertyToName(item.second))});
|
||||
}
|
||||
for (const auto &item : info.unique) {
|
||||
std::vector<TypedValue> properties;
|
||||
properties.reserve(item.second.size());
|
||||
for (const auto &property : item.second) {
|
||||
properties.emplace_back(db->PropertyToName(property));
|
||||
}
|
||||
results.push_back(
|
||||
{TypedValue("unique"), TypedValue(db->LabelToName(item.first)), TypedValue(std::move(properties))});
|
||||
}
|
||||
return std::pair{results, QueryHandlerResult::NOTHING};
|
||||
};
|
||||
break;
|
||||
throw SemanticException("Constraints are not yet supported!");
|
||||
}
|
||||
|
||||
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
|
||||
@ -1990,185 +1815,7 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::vector<Notification> *notifications,
|
||||
InterpreterContext *interpreter_context) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ConstraintInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *constraint_query = utils::Downcast<ConstraintQuery>(parsed_query.query);
|
||||
std::function<void(Notification &)> handler;
|
||||
|
||||
auto label = interpreter_context->db->NameToLabel(constraint_query->constraint_.label.name);
|
||||
std::vector<storage::v3::PropertyId> properties;
|
||||
std::vector<std::string> properties_string;
|
||||
properties.reserve(constraint_query->constraint_.properties.size());
|
||||
properties_string.reserve(constraint_query->constraint_.properties.size());
|
||||
for (const auto &prop : constraint_query->constraint_.properties) {
|
||||
properties.push_back(interpreter_context->db->NameToProperty(prop.name));
|
||||
properties_string.push_back(prop.name);
|
||||
}
|
||||
auto properties_stringified = utils::Join(properties_string, ", ");
|
||||
|
||||
Notification constraint_notification(SeverityLevel::INFO);
|
||||
switch (constraint_query->action_type_) {
|
||||
case ConstraintQuery::ActionType::CREATE: {
|
||||
constraint_notification.code = NotificationCode::CREATE_CONSTRAINT;
|
||||
|
||||
switch (constraint_query->constraint_.type) {
|
||||
case Constraint::Type::NODE_KEY:
|
||||
throw utils::NotYetImplemented("Node key constraints");
|
||||
case Constraint::Type::EXISTS:
|
||||
if (properties.empty() || properties.size() > 1) {
|
||||
throw SyntaxException("Exactly one property must be used for existence constraints.");
|
||||
}
|
||||
constraint_notification.title = fmt::format("Created EXISTS constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, properties_stringified);
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
properties = std::move(properties)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->CreateExistenceConstraint(label, properties[0]);
|
||||
if (res.HasError()) {
|
||||
auto violation = res.GetError();
|
||||
auto label_name = interpreter_context->db->LabelToName(violation.label);
|
||||
MG_ASSERT(violation.properties.size() == 1U);
|
||||
auto property_name = interpreter_context->db->PropertyToName(*violation.properties.begin());
|
||||
throw QueryRuntimeException(
|
||||
"Unable to create existence constraint :{}({}), because an "
|
||||
"existing node violates it.",
|
||||
label_name, property_name);
|
||||
}
|
||||
if (res.HasValue() && !res.GetValue()) {
|
||||
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
|
||||
constraint_notification.title = fmt::format(
|
||||
"Constraint EXISTS on label {} on properties {} already exists.", label_name, properties_stringified);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case Constraint::Type::UNIQUE:
|
||||
std::set<storage::v3::PropertyId> property_set;
|
||||
for (const auto &property : properties) {
|
||||
property_set.insert(property);
|
||||
}
|
||||
if (property_set.size() != properties.size()) {
|
||||
throw SyntaxException("The given set of properties contains duplicates.");
|
||||
}
|
||||
constraint_notification.title =
|
||||
fmt::format("Created UNIQUE constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
property_set = std::move(property_set)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->CreateUniqueConstraint(label, property_set);
|
||||
if (res.HasError()) {
|
||||
auto violation = res.GetError();
|
||||
auto label_name = interpreter_context->db->LabelToName(violation.label);
|
||||
std::stringstream property_names_stream;
|
||||
utils::PrintIterable(property_names_stream, violation.properties, ", ",
|
||||
[&interpreter_context](auto &stream, const auto &prop) {
|
||||
stream << interpreter_context->db->PropertyToName(prop);
|
||||
});
|
||||
throw QueryRuntimeException(
|
||||
"Unable to create unique constraint :{}({}), because an "
|
||||
"existing node violates it.",
|
||||
label_name, property_names_stream.str());
|
||||
}
|
||||
switch (res.GetValue()) {
|
||||
case storage::v3::UniqueConstraints::CreationStatus::EMPTY_PROPERTIES:
|
||||
throw SyntaxException(
|
||||
"At least one property must be used for unique "
|
||||
"constraints.");
|
||||
case storage::v3::UniqueConstraints::CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED:
|
||||
throw SyntaxException(
|
||||
"Too many properties specified. Limit of {} properties "
|
||||
"for unique constraints is exceeded.",
|
||||
storage::v3::kUniqueConstraintsMaxProperties);
|
||||
case storage::v3::UniqueConstraints::CreationStatus::ALREADY_EXISTS:
|
||||
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
|
||||
constraint_notification.title =
|
||||
fmt::format("Constraint UNIQUE on label {} on properties {} already exists.", label_name,
|
||||
properties_stringified);
|
||||
break;
|
||||
case storage::v3::UniqueConstraints::CreationStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case ConstraintQuery::ActionType::DROP: {
|
||||
constraint_notification.code = NotificationCode::DROP_CONSTRAINT;
|
||||
|
||||
switch (constraint_query->constraint_.type) {
|
||||
case Constraint::Type::NODE_KEY:
|
||||
throw utils::NotYetImplemented("Node key constraints");
|
||||
case Constraint::Type::EXISTS:
|
||||
if (properties.empty() || properties.size() > 1) {
|
||||
throw SyntaxException("Exactly one property must be used for existence constraints.");
|
||||
}
|
||||
constraint_notification.title =
|
||||
fmt::format("Dropped EXISTS constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
properties = std::move(properties)](Notification &constraint_notification) {
|
||||
if (!interpreter_context->db->DropExistenceConstraint(label, properties[0])) {
|
||||
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
|
||||
constraint_notification.title = fmt::format(
|
||||
"Constraint EXISTS on label {} on properties {} doesn't exist.", label_name, properties_stringified);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
break;
|
||||
case Constraint::Type::UNIQUE:
|
||||
std::set<storage::v3::PropertyId> property_set;
|
||||
for (const auto &property : properties) {
|
||||
property_set.insert(property);
|
||||
}
|
||||
if (property_set.size() != properties.size()) {
|
||||
throw SyntaxException("The given set of properties contains duplicates.");
|
||||
}
|
||||
constraint_notification.title =
|
||||
fmt::format("Dropped UNIQUE constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
property_set = std::move(property_set)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->DropUniqueConstraint(label, property_set);
|
||||
switch (res) {
|
||||
case storage::v3::UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES:
|
||||
throw SyntaxException(
|
||||
"At least one property must be used for unique "
|
||||
"constraints.");
|
||||
break;
|
||||
case storage::v3::UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED:
|
||||
throw SyntaxException(
|
||||
"Too many properties specified. Limit of {} properties for "
|
||||
"unique constraints is exceeded.",
|
||||
storage::v3::kUniqueConstraintsMaxProperties);
|
||||
break;
|
||||
case storage::v3::UniqueConstraints::DeletionStatus::NOT_FOUND:
|
||||
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
|
||||
constraint_notification.title =
|
||||
fmt::format("Constraint UNIQUE on label {} on properties {} doesn't exist.", label_name,
|
||||
properties_stringified);
|
||||
break;
|
||||
case storage::v3::UniqueConstraints::DeletionStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return PreparedQuery{{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(handler), constraint_notification = std::move(constraint_notification),
|
||||
notifications](AnyStream * /*stream*/, std::optional<int> /*n*/) mutable {
|
||||
handler(constraint_notification);
|
||||
notifications->push_back(constraint_notification);
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
throw SemanticException("Constraint query is not supported!");
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSchemaQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
@ -2262,8 +1909,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
(utils::Downcast<CypherQuery>(parsed_query.query) || utils::Downcast<ExplainQuery>(parsed_query.query) ||
|
||||
utils::Downcast<ProfileQuery>(parsed_query.query) || utils::Downcast<DumpQuery>(parsed_query.query) ||
|
||||
utils::Downcast<TriggerQuery>(parsed_query.query))) {
|
||||
db_accessor_ = std::make_unique<storage::v3::Storage::Accessor>(
|
||||
interpreter_context_->db->Access(GetIsolationLevelOverride()));
|
||||
db_accessor_ = std::make_unique<storage::v3::Shard::Accessor>(
|
||||
interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride()));
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query) && interpreter_context_->trigger_store.HasTriggers()) {
|
||||
@ -2345,13 +1992,6 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
|
||||
UpdateTypeCount(rw_type);
|
||||
|
||||
if (const auto query_type = query_execution->prepared_query->rw_type;
|
||||
interpreter_context_->db->GetReplicationRole() == storage::v3::ReplicationRole::REPLICA &&
|
||||
(query_type == RWType::W || query_type == RWType::RW)) {
|
||||
query_execution = nullptr;
|
||||
throw QueryException("Write query forbidden on the replica!");
|
||||
}
|
||||
|
||||
return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid};
|
||||
} catch (const utils::BasicException &) {
|
||||
EventCounter::IncrementCounter(EventCounter::FailedQuery);
|
||||
@ -2378,7 +2018,7 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
|
||||
utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize};
|
||||
|
||||
// create a new transaction for each trigger
|
||||
auto storage_acc = interpreter_context->db->Access();
|
||||
auto storage_acc = interpreter_context->db->Access(coordinator::Hlc{});
|
||||
DbAccessor db_accessor{&storage_acc};
|
||||
|
||||
trigger_context.AdaptForAccessor(&db_accessor);
|
||||
@ -2391,29 +2031,7 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
|
||||
continue;
|
||||
}
|
||||
|
||||
auto maybe_constraint_violation = db_accessor.Commit();
|
||||
if (maybe_constraint_violation.HasError()) {
|
||||
const auto &constraint_violation = maybe_constraint_violation.GetError();
|
||||
switch (constraint_violation.type) {
|
||||
case storage::v3::ConstraintViolation::Type::EXISTENCE: {
|
||||
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
|
||||
MG_ASSERT(constraint_violation.properties.size() == 1U);
|
||||
const auto &property_name = db_accessor.PropertyToName(*constraint_violation.properties.begin());
|
||||
spdlog::warn("Trigger '{}' failed to commit due to existence constraint violation on :{}({})", trigger.Name(),
|
||||
label_name, property_name);
|
||||
break;
|
||||
}
|
||||
case storage::v3::ConstraintViolation::Type::UNIQUE: {
|
||||
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
|
||||
std::stringstream property_names_stream;
|
||||
utils::PrintIterable(property_names_stream, constraint_violation.properties, ", ",
|
||||
[&](auto &stream, const auto &prop) { stream << db_accessor.PropertyToName(prop); });
|
||||
spdlog::warn("Trigger '{}' failed to commit due to unique constraint violation on :{}({})", trigger.Name(),
|
||||
label_name, property_names_stream.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
db_accessor.Commit();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@ -2454,33 +2072,7 @@ void Interpreter::Commit() {
|
||||
trigger_context_collector_.reset();
|
||||
};
|
||||
|
||||
auto maybe_constraint_violation = db_accessor_->Commit();
|
||||
if (maybe_constraint_violation.HasError()) {
|
||||
const auto &constraint_violation = maybe_constraint_violation.GetError();
|
||||
switch (constraint_violation.type) {
|
||||
case storage::v3::ConstraintViolation::Type::EXISTENCE: {
|
||||
auto label_name = execution_db_accessor_->LabelToName(constraint_violation.label);
|
||||
MG_ASSERT(constraint_violation.properties.size() == 1U);
|
||||
auto property_name = execution_db_accessor_->PropertyToName(*constraint_violation.properties.begin());
|
||||
reset_necessary_members();
|
||||
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
|
||||
property_name);
|
||||
break;
|
||||
}
|
||||
case storage::v3::ConstraintViolation::Type::UNIQUE: {
|
||||
auto label_name = execution_db_accessor_->LabelToName(constraint_violation.label);
|
||||
std::stringstream property_names_stream;
|
||||
utils::PrintIterable(
|
||||
property_names_stream, constraint_violation.properties, ", ",
|
||||
[this](auto &stream, const auto &prop) { stream << execution_db_accessor_->PropertyToName(prop); });
|
||||
reset_necessary_members();
|
||||
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
|
||||
property_names_stream.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db_accessor_->Commit(coordinator::Hlc{});
|
||||
// The ordered execution of after commit triggers is heavily depending on the exclusiveness of db_accessor_->Commit():
|
||||
// only one of the transactions can be commiting at the same time, so when the commit is finished, that transaction
|
||||
// probably will schedule its after commit triggers, because the other transactions that want to commit are still
|
||||
@ -2492,7 +2084,6 @@ void Interpreter::Commit() {
|
||||
user_transaction = std::shared_ptr(std::move(db_accessor_))]() mutable {
|
||||
RunTriggersIndividually(interpreter_context->trigger_store.AfterCommitTriggers(), interpreter_context,
|
||||
std::move(trigger_context));
|
||||
user_transaction->FinalizeTransaction();
|
||||
SPDLOG_DEBUG("Finished executing after commit triggers"); // NOLINT(bugprone-lambda-function-name)
|
||||
});
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "query/v2/stream/streams.hpp"
|
||||
#include "query/v2/trigger.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
@ -165,10 +166,10 @@ struct PreparedQuery {
|
||||
* been passed to an `Interpreter` instance.
|
||||
*/
|
||||
struct InterpreterContext {
|
||||
explicit InterpreterContext(storage::v3::Storage *db, InterpreterConfig config,
|
||||
explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory);
|
||||
|
||||
storage::v3::Storage *db;
|
||||
storage::v3::Shard *db;
|
||||
|
||||
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
|
||||
std::atomic<bool> is_shutting_down{false};
|
||||
@ -185,6 +186,22 @@ struct InterpreterContext {
|
||||
const InterpreterConfig config;
|
||||
|
||||
query::v2::stream::Streams streams;
|
||||
|
||||
storage::v3::LabelId NameToLabelId(std::string_view label_name) {
|
||||
return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name));
|
||||
}
|
||||
|
||||
storage::v3::PropertyId NameToPropertyId(std::string_view property_name) {
|
||||
return storage::v3::PropertyId::FromUint(query_id_mapper.NameToId(property_name));
|
||||
}
|
||||
|
||||
storage::v3::EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) {
|
||||
return storage::v3::EdgeTypeId::FromUint(query_id_mapper.NameToId(edge_type_name));
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO Replace with local map of labels, properties and edge type ids
|
||||
storage::v3::NameIdMapper query_id_mapper;
|
||||
};
|
||||
|
||||
/// Function that is used to tell all active interpreters that they should stop
|
||||
@ -315,7 +332,7 @@ class Interpreter final {
|
||||
// This cannot be std::optional because we need to move this accessor later on into a lambda capture
|
||||
// which is assigned to std::function. std::function requires every object to be copyable, so we
|
||||
// move this unique_ptr into a shrared_ptr.
|
||||
std::unique_ptr<storage::v3::Storage::Accessor> db_accessor_;
|
||||
std::unique_ptr<storage::v3::Shard::Accessor> db_accessor_;
|
||||
std::optional<DbAccessor> execution_db_accessor_;
|
||||
std::optional<TriggerContextCollector> trigger_context_collector_;
|
||||
bool in_explicit_transaction_{false};
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/pmr/unordered_map.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
@ -608,17 +609,16 @@ ACCEPT_WITH_INPUT(ScanAllById)
|
||||
|
||||
UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const {
|
||||
EventCounter::IncrementCounter(EventCounter::ScanAllByIdOperator);
|
||||
|
||||
// TODO Reimplement when we have reliable conversion between hash value and pk
|
||||
auto vertices = [this](Frame &frame, ExecutionContext &context) -> std::optional<std::vector<VertexAccessor>> {
|
||||
auto *db = context.db_accessor;
|
||||
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, view_);
|
||||
auto value = expression_->Accept(evaluator);
|
||||
if (!value.IsNumeric()) return std::nullopt;
|
||||
int64_t id = value.IsInt() ? value.ValueInt() : value.ValueDouble();
|
||||
if (value.IsDouble() && id != value.ValueDouble()) return std::nullopt;
|
||||
auto maybe_vertex = db->FindVertex(storage::v3::Gid::FromInt(id), view_);
|
||||
if (!maybe_vertex) return std::nullopt;
|
||||
return std::vector<VertexAccessor>{*maybe_vertex};
|
||||
// auto *db = context.db_accessor;
|
||||
// ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
|
||||
// view_); auto value = expression_->Accept(evaluator); if (!value.IsNumeric()) return std::nullopt; int64_t id =
|
||||
// value.IsInt() ? value.ValueInt() : value.ValueDouble(); if (value.IsDouble() && id != value.ValueDouble()) return
|
||||
// std::nullopt; auto maybe_vertex = db->FindVertex(storage::v3::Gid::FromInt(id), view_); auto maybe_vertex =
|
||||
// nullptr; if (!maybe_vertex) return std::nullopt;
|
||||
return std::nullopt;
|
||||
// return std::vector<VertexAccessor>{*maybe_vertex};
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllById");
|
||||
@ -2139,7 +2139,7 @@ concept AccessorWithProperties = requires(T value, storage::v3::PropertyId prope
|
||||
///
|
||||
/// @tparam TRecordAccessor Either RecordAccessor<Vertex> or
|
||||
/// RecordAccessor<Edge>
|
||||
template <AccessorWithProperties TRecordAccessor>
|
||||
template <RecordAccessor TRecordAccessor>
|
||||
void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op,
|
||||
ExecutionContext *context) {
|
||||
std::optional<std::map<storage::v3::PropertyId, storage::v3::PropertyValue>> old_values;
|
||||
@ -2202,23 +2202,26 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
||||
|
||||
auto set_props = [&, record](auto properties) {
|
||||
for (auto &kv : properties) {
|
||||
auto maybe_error = record->SetProperty(kv.first, kv.second);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted graph element.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting properties.");
|
||||
if constexpr (AccessorWithSetPropertyAndValidate<TRecordAccessor>) {
|
||||
const auto maybe_error = record->SetPropertyAndValidate(kv.first, storage::v3::PropertyValue(kv.second));
|
||||
if (maybe_error.HasError()) {
|
||||
std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); },
|
||||
[&context](const storage::v3::SchemaViolation &schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, *context->db_accessor);
|
||||
}},
|
||||
maybe_error.GetError());
|
||||
}
|
||||
if (should_register_change) {
|
||||
register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second));
|
||||
}
|
||||
} else {
|
||||
auto maybe_error = record->SetProperty(kv.first, kv.second);
|
||||
if (maybe_error.HasError()) {
|
||||
HandleErrorOnPropertyUpdate(maybe_error.GetError());
|
||||
}
|
||||
if (should_register_change) {
|
||||
register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second));
|
||||
}
|
||||
}
|
||||
|
||||
if (should_register_change) {
|
||||
register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "query/v2/procedure/mg_procedure_helpers.hpp"
|
||||
#include "query/v2/stream/common.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
@ -1510,8 +1511,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
|
||||
result);
|
||||
}
|
||||
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) {
|
||||
return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result);
|
||||
return WrapExceptions([] { return mgp_vertex_id{.as_int = 0}; }, result);
|
||||
}
|
||||
|
||||
mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
|
||||
@ -1586,17 +1588,19 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
const auto prop_key = v->graph->impl->NameToProperty(property_name);
|
||||
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"};
|
||||
case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a vertex!");
|
||||
case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
LOG_FATAL("Unexpected error when setting a property of a vertex.");
|
||||
case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw SerializationException{"Cannot serialize setting a property of a vertex."};
|
||||
}
|
||||
// TODO(jbajic) Fix query modules
|
||||
// switch (result.GetError()) {
|
||||
// case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
// throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"};
|
||||
// case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
// LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a
|
||||
// vertex!");
|
||||
// case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
// case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
// LOG_FATAL("Unexpected error when setting a property of a vertex.");
|
||||
// case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
// throw SerializationException{"Cannot serialize setting a property of a vertex."};
|
||||
// }
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
@ -1628,17 +1632,18 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
const auto result = v->impl.AddLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
throw DeletedObjectException{"Cannot add a label to a deleted vertex!"};
|
||||
case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when adding a label to a vertex!");
|
||||
case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
LOG_FATAL("Unexpected error when adding a label to a vertex.");
|
||||
case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw SerializationException{"Cannot serialize adding a label to a vertex."};
|
||||
}
|
||||
// TODO(jbajic) Fix query modules
|
||||
// switch (result.GetError()) {
|
||||
// case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
// throw DeletedObjectException{"Cannot add a label to a deleted vertex!"};
|
||||
// case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
// LOG_FATAL("Query modules shouldn't have access to nonexistent objects when adding a label to a vertex!");
|
||||
// case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
// case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
// LOG_FATAL("Unexpected error when adding a label to a vertex.");
|
||||
// case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
// throw SerializationException{"Cannot serialize adding a label to a vertex."};
|
||||
// }
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
@ -1660,17 +1665,19 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
const auto result = v->impl.RemoveLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"};
|
||||
case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a label from a vertex!");
|
||||
case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
LOG_FATAL("Unexpected error when removing a label from a vertex.");
|
||||
case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw SerializationException{"Cannot serialize removing a label from a vertex."};
|
||||
}
|
||||
// TODO(jbajic) Fix query modules
|
||||
// switch (result.GetError()) {
|
||||
// case memgraph::storage::v3::Error::DELETED_OBJECT:
|
||||
// throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"};
|
||||
// case memgraph::storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
// LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a label from a
|
||||
// vertex!");
|
||||
// case memgraph::storage::v3::Error::PROPERTIES_DISABLED:
|
||||
// case memgraph::storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
// LOG_FATAL("Unexpected error when removing a label from a vertex.");
|
||||
// case memgraph::storage::v3::Error::SERIALIZATION_ERROR:
|
||||
// throw SerializationException{"Cannot serialize removing a label from a vertex."};
|
||||
// }
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
@ -2074,11 +2081,12 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
|
||||
|
||||
mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
|
||||
return WrapExceptions(
|
||||
[graph, id, memory]() -> mgp_vertex * {
|
||||
auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::v3::Gid::FromInt(id.as_int), graph->view);
|
||||
if (maybe_vertex) {
|
||||
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
}
|
||||
[]() -> mgp_vertex * {
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// auto maybe_vertex = graph->impl->FindVertex(0);
|
||||
// if (maybe_vertex) {
|
||||
// return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
// }
|
||||
return nullptr;
|
||||
},
|
||||
result);
|
||||
@ -2089,23 +2097,25 @@ mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
};
|
||||
|
||||
mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) {
|
||||
return WrapExceptions(
|
||||
[=] {
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
|
||||
}
|
||||
auto vertex = graph->impl->InsertVertex();
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
mgp_error mgp_graph_create_vertex(struct mgp_graph * /*graph*/, mgp_memory * /*memory*/, mgp_vertex ** /*result*/) {
|
||||
// return WrapExceptions(
|
||||
// [=] {
|
||||
// if (!MgpGraphIsMutable(*graph)) {
|
||||
// throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
|
||||
// }
|
||||
// auto vertex = graph->impl->InsertVertex();
|
||||
|
||||
auto &ctx = graph->ctx;
|
||||
ctx->execution_stats[memgraph::query::v2::ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
// auto &ctx = graph->ctx;
|
||||
// ctx->execution_stats[memgraph::query::v2::ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(vertex);
|
||||
}
|
||||
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
|
||||
},
|
||||
result);
|
||||
// if (ctx->trigger_context_collector) {
|
||||
// ctx->trigger_context_collector->RegisterCreatedObject(vertex);
|
||||
// }
|
||||
// return NewRawMgpObject<mgp_vertex>(memory, nullptr, graph);
|
||||
// },
|
||||
// result);
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
|
||||
@ -2177,11 +2187,11 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
|
||||
if (!trigger_ctx_collector) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger_ctx_collector->RegisterDeletedObject((*result)->first);
|
||||
if (!trigger_ctx_collector->ShouldRegisterDeletedObject<memgraph::query::v2::EdgeAccessor>()) {
|
||||
return;
|
||||
}
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// trigger_ctx_collector->RegisterDeletedObject((*result)->first);
|
||||
// if (!trigger_ctx_collector->ShouldRegisterDeletedObject<memgraph::query::v2::EdgeAccessor>()) {
|
||||
// return;
|
||||
// }
|
||||
for (const auto &edge : (*result)->second) {
|
||||
trigger_ctx_collector->RegisterDeletedObject(edge);
|
||||
}
|
||||
|
@ -474,9 +474,10 @@ struct mgp_edge {
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_edge>;
|
||||
|
||||
// TODO(antaljanosbenjamin): Handle this static assert failure when we will support procedures again
|
||||
// Hopefully EdgeAccessor copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::v2::EdgeAccessor>);
|
||||
// static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::v2::EdgeAccessor>);
|
||||
|
||||
static mgp_edge *Copy(const mgp_edge &edge, mgp_memory &memory);
|
||||
|
||||
|
@ -336,9 +336,10 @@ PyObject *PyGraphCreateVertex(PyGraph *self, PyObject *Py_UNUSED(ignored)) {
|
||||
MG_ASSERT(PyGraphIsValidImpl(*self));
|
||||
MG_ASSERT(self->memory);
|
||||
MgpUniquePtr<mgp_vertex> new_vertex{nullptr, mgp_vertex_destroy};
|
||||
if (RaiseExceptionFromErrorCode(CreateMgpObject(new_vertex, mgp_graph_create_vertex, self->graph, self->memory))) {
|
||||
return nullptr;
|
||||
}
|
||||
// TODO(jbajic) Fix query module
|
||||
// if (RaiseExceptionFromErrorCode(CreateMgpObject(new_vertex, mgp_graph_create_vertex, self->graph, self->memory))) {
|
||||
// return nullptr;
|
||||
// }
|
||||
auto *py_vertex = MakePyVertexWithoutCopy(*new_vertex, self);
|
||||
if (py_vertex != nullptr) {
|
||||
static_cast<void>(new_vertex.release());
|
||||
|
@ -16,52 +16,51 @@
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
|
||||
/// Hybrid-logical clock
|
||||
struct Hlc {
|
||||
uint64_t logical_id;
|
||||
using Duration = std::chrono::microseconds;
|
||||
using Time = std::chrono::time_point<std::chrono::system_clock, Duration>;
|
||||
Time coordinator_wall_clock;
|
||||
namespace memgraph::msgs {
|
||||
|
||||
bool operator==(const Hlc &other) const = default;
|
||||
};
|
||||
using coordinator::Hlc;
|
||||
using storage::v3::LabelId;
|
||||
|
||||
struct Value;
|
||||
|
||||
struct Label {
|
||||
size_t id;
|
||||
LabelId id;
|
||||
};
|
||||
|
||||
// TODO(kostasrim) update this with CompoundKey, same for the rest of the file.
|
||||
using PrimaryKey = std::vector<memgraph::storage::v3::PropertyValue>;
|
||||
using PrimaryKey = std::vector<Value>;
|
||||
using VertexId = std::pair<Label, PrimaryKey>;
|
||||
using Gid = size_t;
|
||||
using PropertyId = memgraph::storage::v3::PropertyId;
|
||||
|
||||
struct EdgeType {
|
||||
std::string name;
|
||||
uint64_t id;
|
||||
};
|
||||
|
||||
struct EdgeId {
|
||||
VertexId id;
|
||||
Gid gid;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
VertexId id;
|
||||
std::vector<Label> labels;
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
VertexId src;
|
||||
VertexId dst;
|
||||
EdgeId id;
|
||||
EdgeType type;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
VertexId id;
|
||||
std::vector<Label> labels;
|
||||
};
|
||||
|
||||
struct PathPart {
|
||||
Vertex dst;
|
||||
Gid edge;
|
||||
@ -75,11 +74,224 @@ struct Path {
|
||||
struct Null {};
|
||||
|
||||
struct Value {
|
||||
enum Type { NILL, BOOL, INT64, DOUBLE, STRING, LIST, MAP, VERTEX, EDGE, PATH };
|
||||
Value() : null_v{} {}
|
||||
|
||||
explicit Value(const bool val) : type(Type::Bool), bool_v(val) {}
|
||||
explicit Value(const int64_t val) : type(Type::Int64), int_v(val) {}
|
||||
explicit Value(const double val) : type(Type::Double), double_v(val) {}
|
||||
|
||||
explicit Value(const Vertex val) : type(Type::Vertex), vertex_v(val) {}
|
||||
|
||||
explicit Value(const std::string &val) : type(Type::String) { new (&string_v) std::string(val); }
|
||||
explicit Value(const char *val) : type(Type::String) { new (&string_v) std::string(val); }
|
||||
|
||||
explicit Value(const std::vector<Value> &val) : type(Type::List) { new (&list_v) std::vector<Value>(val); }
|
||||
|
||||
explicit Value(const std::map<std::string, Value> &val) : type(Type::Map) {
|
||||
new (&map_v) std::map<std::string, Value>(val);
|
||||
}
|
||||
|
||||
explicit Value(std::string &&val) noexcept : type(Type::String) { new (&string_v) std::string(std::move(val)); }
|
||||
|
||||
explicit Value(std::vector<Value> &&val) noexcept : type(Type::List) {
|
||||
new (&list_v) std::vector<Value>(std::move(val));
|
||||
}
|
||||
explicit Value(std::map<std::string, Value> &&val) noexcept : type(Type::Map) {
|
||||
new (&map_v) std::map<std::string, Value>(std::move(val));
|
||||
}
|
||||
|
||||
~Value() { DestroyValue(); }
|
||||
|
||||
void DestroyValue() noexcept {
|
||||
switch (type) {
|
||||
case Type::Null:
|
||||
case Type::Bool:
|
||||
case Type::Int64:
|
||||
case Type::Double:
|
||||
return;
|
||||
|
||||
case Type::String:
|
||||
std::destroy_at(&string_v);
|
||||
return;
|
||||
case Type::List:
|
||||
std::destroy_at(&list_v);
|
||||
return;
|
||||
case Type::Map:
|
||||
std::destroy_at(&map_v);
|
||||
return;
|
||||
|
||||
case Type::Vertex:
|
||||
std::destroy_at(&vertex_v);
|
||||
return;
|
||||
case Type::Path:
|
||||
std::destroy_at(&path_v);
|
||||
return;
|
||||
case Type::Edge:
|
||||
std::destroy_at(&edge_v);
|
||||
}
|
||||
}
|
||||
|
||||
Value(const Value &other) : type(other.type) {
|
||||
switch (other.type) {
|
||||
case Type::Null:
|
||||
return;
|
||||
case Type::Bool:
|
||||
this->bool_v = other.bool_v;
|
||||
return;
|
||||
case Type::Int64:
|
||||
this->int_v = other.int_v;
|
||||
return;
|
||||
case Type::Double:
|
||||
this->double_v = other.double_v;
|
||||
return;
|
||||
case Type::String:
|
||||
new (&string_v) std::string(other.string_v);
|
||||
return;
|
||||
case Type::List:
|
||||
new (&list_v) std::vector<Value>(other.list_v);
|
||||
return;
|
||||
case Type::Map:
|
||||
new (&map_v) std::map<std::string, Value>(other.map_v);
|
||||
return;
|
||||
case Type::Vertex:
|
||||
new (&vertex_v) Vertex(other.vertex_v);
|
||||
return;
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(other.edge_v);
|
||||
return;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Value(Value &&other) noexcept : type(other.type) {
|
||||
switch (other.type) {
|
||||
case Type::Null:
|
||||
break;
|
||||
case Type::Bool:
|
||||
this->bool_v = other.bool_v;
|
||||
break;
|
||||
case Type::Int64:
|
||||
this->int_v = other.int_v;
|
||||
break;
|
||||
case Type::Double:
|
||||
this->double_v = other.double_v;
|
||||
break;
|
||||
case Type::String:
|
||||
new (&string_v) std::string(std::move(other.string_v));
|
||||
break;
|
||||
case Type::List:
|
||||
new (&list_v) std::vector<Value>(std::move(other.list_v));
|
||||
break;
|
||||
case Type::Map:
|
||||
new (&map_v) std::map<std::string, Value>(std::move(other.map_v));
|
||||
break;
|
||||
case Type::Vertex:
|
||||
new (&vertex_v) Vertex(std::move(other.vertex_v));
|
||||
break;
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(std::move(other.edge_v));
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
}
|
||||
|
||||
other.DestroyValue();
|
||||
other.type = Type::Null;
|
||||
}
|
||||
|
||||
Value &operator=(const Value &other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
DestroyValue();
|
||||
type = other.type;
|
||||
|
||||
switch (other.type) {
|
||||
case Type::Null:
|
||||
break;
|
||||
case Type::Bool:
|
||||
this->bool_v = other.bool_v;
|
||||
break;
|
||||
case Type::Int64:
|
||||
this->int_v = other.int_v;
|
||||
break;
|
||||
case Type::Double:
|
||||
this->double_v = other.double_v;
|
||||
break;
|
||||
case Type::String:
|
||||
new (&string_v) std::string(other.string_v);
|
||||
break;
|
||||
case Type::List:
|
||||
new (&list_v) std::vector<Value>(other.list_v);
|
||||
break;
|
||||
case Type::Map:
|
||||
new (&map_v) std::map<std::string, Value>(other.map_v);
|
||||
break;
|
||||
case Type::Vertex:
|
||||
new (&vertex_v) Vertex(other.vertex_v);
|
||||
break;
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(other.edge_v);
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Value &operator=(Value &&other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
|
||||
DestroyValue();
|
||||
type = other.type;
|
||||
|
||||
switch (other.type) {
|
||||
case Type::Null:
|
||||
break;
|
||||
case Type::Bool:
|
||||
this->bool_v = other.bool_v;
|
||||
break;
|
||||
case Type::Int64:
|
||||
this->int_v = other.int_v;
|
||||
break;
|
||||
case Type::Double:
|
||||
this->double_v = other.double_v;
|
||||
break;
|
||||
case Type::String:
|
||||
new (&string_v) std::string(std::move(other.string_v));
|
||||
break;
|
||||
case Type::List:
|
||||
new (&list_v) std::vector<Value>(std::move(other.list_v));
|
||||
break;
|
||||
case Type::Map:
|
||||
new (&map_v) std::map<std::string, Value>(std::move(other.map_v));
|
||||
break;
|
||||
case Type::Vertex:
|
||||
new (&vertex_v) Vertex(std::move(other.vertex_v));
|
||||
break;
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(std::move(other.edge_v));
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
}
|
||||
|
||||
other.DestroyValue();
|
||||
other.type = Type::Null;
|
||||
|
||||
return *this;
|
||||
}
|
||||
enum class Type : uint8_t { Null, Bool, Int64, Double, String, List, Map, Vertex, Edge, Path };
|
||||
Type type{Type::Null};
|
||||
union {
|
||||
Null null_v;
|
||||
bool bool_v;
|
||||
uint64_t int_v;
|
||||
int64_t int_v;
|
||||
double double_v;
|
||||
std::string string_v;
|
||||
std::vector<Value> list_v;
|
||||
@ -88,8 +300,6 @@ struct Value {
|
||||
Edge edge_v;
|
||||
Path path_v;
|
||||
};
|
||||
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct ValuesMap {
|
||||
@ -125,17 +335,23 @@ enum class StorageView { OLD = 0, NEW = 1 };
|
||||
|
||||
struct ScanVerticesRequest {
|
||||
Hlc transaction_id;
|
||||
size_t start_id;
|
||||
std::optional<std::vector<std::string>> props_to_return;
|
||||
VertexId start_id;
|
||||
std::optional<std::vector<PropertyId>> props_to_return;
|
||||
std::optional<std::vector<std::string>> filter_expressions;
|
||||
std::optional<size_t> batch_limit;
|
||||
StorageView storage_view;
|
||||
};
|
||||
|
||||
struct ScanResultRow {
|
||||
Value vertex;
|
||||
// empty() is no properties returned
|
||||
std::map<PropertyId, Value> props;
|
||||
};
|
||||
|
||||
struct ScanVerticesResponse {
|
||||
bool success;
|
||||
Values values;
|
||||
std::optional<VertexId> next_start_id;
|
||||
std::vector<ScanResultRow> results;
|
||||
};
|
||||
|
||||
using VertexOrEdgeIds = std::variant<VertexId, EdgeId>;
|
||||
@ -202,9 +418,23 @@ struct ExpandOneResponse {
|
||||
std::vector<ExpandOneResultRow> result;
|
||||
};
|
||||
|
||||
struct UpdateVertexProp {
|
||||
VertexId primary_key;
|
||||
std::vector<std::pair<PropertyId, Value>> property_updates;
|
||||
};
|
||||
|
||||
struct UpdateEdgeProp {
|
||||
Edge edge;
|
||||
std::vector<std::pair<PropertyId, Value>> property_updates;
|
||||
};
|
||||
|
||||
/*
|
||||
* Vertices
|
||||
*/
|
||||
struct NewVertex {
|
||||
std::vector<Label> label_ids;
|
||||
std::map<PropertyId, Value> properties;
|
||||
PrimaryKey primary_key;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
struct CreateVerticesRequest {
|
||||
@ -216,8 +446,71 @@ struct CreateVerticesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct DeleteVerticesRequest {
|
||||
enum class DeletionType { DELETE, DETACH_DELETE };
|
||||
Hlc transaction_id;
|
||||
std::vector<std::vector<Value>> primary_keys;
|
||||
DeletionType deletion_type;
|
||||
};
|
||||
|
||||
struct DeleteVerticesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct UpdateVerticesRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<UpdateVertexProp> new_properties;
|
||||
};
|
||||
|
||||
struct UpdateVerticesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
/*
|
||||
* Edges
|
||||
*/
|
||||
struct CreateEdgesRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<Edge> edges;
|
||||
};
|
||||
|
||||
struct CreateEdgesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct DeleteEdgesRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<Edge> edges;
|
||||
};
|
||||
|
||||
struct DeleteEdgesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct UpdateEdgesRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<UpdateEdgeProp> new_properties;
|
||||
};
|
||||
|
||||
struct UpdateEdgesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct CommitRequest {
|
||||
Hlc transaction_id;
|
||||
Hlc commit_timestamp;
|
||||
};
|
||||
|
||||
struct CommitResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
using ReadRequests = std::variant<ExpandOneRequest, GetPropertiesRequest, ScanVerticesRequest>;
|
||||
using ReadResponses = std::variant<ExpandOneResponse, GetPropertiesResponse, ScanVerticesResponse>;
|
||||
|
||||
using WriteRequests = CreateVerticesRequest;
|
||||
using WriteResponses = CreateVerticesResponse;
|
||||
using WriteRequests = std::variant<CreateVerticesRequest, DeleteVerticesRequest, UpdateVerticesRequest,
|
||||
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
|
||||
using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesResponse, UpdateVerticesResponse,
|
||||
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;
|
||||
|
||||
} // namespace memgraph::msgs
|
||||
|
@ -84,7 +84,7 @@ std::pair<TypedValue /*query*/, TypedValue /*parameters*/> ExtractTransformation
|
||||
|
||||
template <typename TMessage>
|
||||
void CallCustomTransformation(const std::string &transformation_name, const std::vector<TMessage> &messages,
|
||||
mgp_result &result, storage::v3::Storage::Accessor &storage_accessor,
|
||||
mgp_result &result, storage::v3::Shard::Accessor &storage_accessor,
|
||||
utils::MemoryResource &memory_resource, const std::string &stream_name) {
|
||||
DbAccessor db_accessor{&storage_accessor};
|
||||
{
|
||||
@ -490,7 +490,7 @@ Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std
|
||||
total_retries = interpreter_context_->config.stream_transaction_conflict_retries,
|
||||
retry_interval = interpreter_context_->config.stream_transaction_retry_interval](
|
||||
const std::vector<typename TStream::Message> &messages) mutable {
|
||||
auto accessor = interpreter_context->db->Access();
|
||||
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
|
||||
EventCounter::IncrementCounter(EventCounter::MessagesConsumed, messages.size());
|
||||
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
|
||||
|
||||
@ -738,7 +738,7 @@ TransformationResult Streams::Check(const std::string &stream_name, std::optiona
|
||||
auto consumer_function = [interpreter_context = interpreter_context_, memory_resource, &stream_name,
|
||||
&transformation_name = transformation_name, &result,
|
||||
&test_result]<typename T>(const std::vector<T> &messages) mutable {
|
||||
auto accessor = interpreter_context->db->Access();
|
||||
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
|
||||
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
|
||||
|
||||
auto result_row = std::vector<TypedValue>();
|
||||
|
@ -264,8 +264,8 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
{
|
||||
// adapt created_vertices_
|
||||
auto it = created_vertices_.begin();
|
||||
for (auto &created_vertex : created_vertices_) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(created_vertex.object.Gid(), storage::v3::View::OLD); maybe_vertex) {
|
||||
for ([[maybe_unused]] auto &created_vertex : created_vertices_) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) {
|
||||
*it = detail::CreatedObject{*maybe_vertex};
|
||||
++it;
|
||||
}
|
||||
@ -280,7 +280,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
const auto adapt_context_with_vertex = [accessor](auto *values) {
|
||||
auto it = values->begin();
|
||||
for (auto &value : *values) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(value.object.Gid(), storage::v3::View::OLD); maybe_vertex) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) {
|
||||
*it = std::move(value);
|
||||
it->object = *maybe_vertex;
|
||||
++it;
|
||||
@ -298,7 +298,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
// adapt created_edges
|
||||
auto it = created_edges_.begin();
|
||||
for (auto &created_edge : created_edges_) {
|
||||
const auto maybe_from_vertex = accessor->FindVertex(created_edge.object.From().Gid(), storage::v3::View::OLD);
|
||||
const auto maybe_from_vertex = accessor->FindVertex(kFakeVertexGid);
|
||||
if (!maybe_from_vertex) {
|
||||
continue;
|
||||
}
|
||||
@ -322,7 +322,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
const auto adapt_context_with_edge = [accessor](auto *values) {
|
||||
auto it = values->begin();
|
||||
for (const auto &value : *values) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::v3::View::OLD); maybe_vertex) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) {
|
||||
auto maybe_out_edges = maybe_vertex->OutEdges(storage::v3::View::OLD);
|
||||
MG_ASSERT(maybe_out_edges.HasValue());
|
||||
for (const auto &edge : *maybe_out_edges) {
|
||||
@ -435,7 +435,7 @@ bool TriggerContext::ShouldEventTrigger(const TriggerEventType event_type) const
|
||||
void TriggerContextCollector::UpdateLabelMap(const VertexAccessor vertex, const storage::v3::LabelId label_id,
|
||||
const LabelChange change) {
|
||||
auto ®istry = GetRegistry<VertexAccessor>();
|
||||
if (!registry.should_register_updated_objects || registry.created_objects.count(vertex.Gid())) {
|
||||
if (!registry.should_register_updated_objects || registry.created_objects.count(kFakeVertexGid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,16 @@
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
// TODO(jbajic) Fix triggers
|
||||
inline constexpr uint64_t kFakeVertexGid{0};
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
concept ObjectAccessor = utils::SameAsAnyOf<T, VertexAccessor, EdgeAccessor>;
|
||||
@ -162,8 +166,10 @@ const char *TriggerEventTypeToString(TriggerEventType event_type);
|
||||
|
||||
static_assert(std::is_trivially_copy_constructible_v<VertexAccessor>,
|
||||
"VertexAccessor is not trivially copy constructible, move it where possible and remove this assert");
|
||||
static_assert(std::is_trivially_copy_constructible_v<EdgeAccessor>,
|
||||
"EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert");
|
||||
// TODO(antaljanosbenjamin): Either satisfy this static_assert or move the edge accessors where it is possible when we
|
||||
// will support triggers.
|
||||
// static_assert(std::is_trivially_copy_constructible_v<EdgeAccessor>,
|
||||
// "EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert");
|
||||
|
||||
// Holds the information necessary for triggers
|
||||
class TriggerContext {
|
||||
@ -223,8 +229,13 @@ class TriggerContextCollector {
|
||||
struct HashPairWithAccessor {
|
||||
template <detail::ObjectAccessor TAccessor, typename T2>
|
||||
size_t operator()(const std::pair<TAccessor, T2> &pair) const {
|
||||
using GidType = decltype(std::declval<TAccessor>().Gid());
|
||||
return utils::HashCombine<GidType, T2>{}(pair.first.Gid(), pair.second);
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
if constexpr (std::is_same_v<TAccessor, VertexAccessor>) {
|
||||
return utils::HashCombine<uint64_t, T2>{}(kFakeVertexGid, pair.second);
|
||||
} else {
|
||||
using UniqueIdentifierType = decltype(std::declval<TAccessor>().Gid());
|
||||
return utils::HashCombine<UniqueIdentifierType, T2>{}(pair.first.Gid(), pair.second);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -239,10 +250,12 @@ class TriggerContextCollector {
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct Registry {
|
||||
using UniqueIdentifier =
|
||||
typename std::conditional_t<(std::is_same_v<TAccessor, VertexAccessor>), uint64_t, storage::v3::Gid>;
|
||||
bool should_register_created_objects{false};
|
||||
bool should_register_deleted_objects{false};
|
||||
bool should_register_updated_objects{false}; // Set/removed properties (and labels for vertices)
|
||||
std::unordered_map<storage::v3::Gid, detail::CreatedObject<TAccessor>> created_objects;
|
||||
std::unordered_map<UniqueIdentifier, detail::CreatedObject<TAccessor>> created_objects;
|
||||
std::vector<detail::DeletedObject<TAccessor>> deleted_objects;
|
||||
// During the transaction, a single property on a single object could be changed multiple times.
|
||||
// We want to register only the global change, at the end of the transaction. The change consists of
|
||||
@ -268,7 +281,11 @@ class TriggerContextCollector {
|
||||
if (!registry.should_register_created_objects) {
|
||||
return;
|
||||
}
|
||||
registry.created_objects.emplace(created_object.Gid(), detail::CreatedObject{created_object});
|
||||
if constexpr (std::is_same_v<TAccessor, VertexAccessor>) {
|
||||
registry.created_objects.emplace(kFakeVertexGid, detail::CreatedObject{created_object});
|
||||
} else {
|
||||
registry.created_objects.emplace(created_object.Gid(), detail::CreatedObject{created_object});
|
||||
}
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
@ -276,9 +293,8 @@ class TriggerContextCollector {
|
||||
return GetRegistry<TAccessor>().should_register_deleted_objects;
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
void RegisterDeletedObject(const TAccessor &deleted_object) {
|
||||
auto ®istry = GetRegistry<TAccessor>();
|
||||
void RegisterDeletedObject(const EdgeAccessor &deleted_object) {
|
||||
auto ®istry = GetRegistry<EdgeAccessor>();
|
||||
if (!registry.should_register_deleted_objects || registry.created_objects.count(deleted_object.Gid())) {
|
||||
return;
|
||||
}
|
||||
@ -286,6 +302,10 @@ class TriggerContextCollector {
|
||||
registry.deleted_objects.emplace_back(deleted_object);
|
||||
}
|
||||
|
||||
void RegisterDeletedObject(const VertexAccessor &deleted_object) {
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
bool ShouldRegisterObjectPropertyChange() const {
|
||||
return GetRegistry<TAccessor>().should_register_updated_objects;
|
||||
@ -298,9 +318,14 @@ class TriggerContextCollector {
|
||||
if (!registry.should_register_updated_objects) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (registry.created_objects.count(object.Gid())) {
|
||||
return;
|
||||
if constexpr (std::is_same_v<TAccessor, VertexAccessor>) {
|
||||
if (registry.created_objects.count(kFakeVertexGid)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (registry.created_objects.count(object.Gid())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto it = registry.property_changes.find({object, key}); it != registry.property_changes.end()) {
|
||||
|
@ -1110,8 +1110,9 @@ void Storage::Accessor::Abort() {
|
||||
{
|
||||
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
|
||||
uint64_t mark_timestamp = storage_->timestamp_;
|
||||
// Take garbage_undo_buffers lock while holding the engine lock to make
|
||||
// sure that entries are sorted by mark timestamp in the list.
|
||||
// Take garbage_undo_buffers lock while holding the engine lock to make sure that entries are sorted by mark
|
||||
// timestamp in the list. This is necessary when a transaction is aborting simultaneously with a GC run: both of
|
||||
// these operations acquire a mark timestamps and then modify the garbage deltas.
|
||||
storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
|
||||
// Release engine lock because we don't have to hold it anymore and
|
||||
// emplace back could take a long time.
|
||||
@ -1517,8 +1518,9 @@ void Storage::CollectGarbage() {
|
||||
{
|
||||
std::unique_lock<utils::SpinLock> guard(engine_lock_);
|
||||
uint64_t mark_timestamp = timestamp_;
|
||||
// Take garbage_undo_buffers lock while holding the engine lock to make
|
||||
// sure that entries are sorted by mark timestamp in the list.
|
||||
// Take garbage_undo_buffers lock while holding the engine lock to make sure that entries are sorted by mark
|
||||
// timestamp in the list. This is necessary when a transaction is aborting simultaneously with a GC run: both of
|
||||
// these operations acquire a mark timestamps and then modify the garbage deltas.
|
||||
garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
|
||||
// Release engine lock because we don't have to hold it anymore and
|
||||
// this could take a long time.
|
||||
|
@ -1,40 +1,33 @@
|
||||
define_add_lcp(add_storage_ast lcp_storage_v3_cpp_files generated_lcp_storage_v3_files)
|
||||
|
||||
add_storage_ast(bindings/ast/ast.lcp)
|
||||
|
||||
add_custom_target(generate_lcp_ast_storage_v3 DEPENDS ${generated_lcp_storage_v3_files})
|
||||
|
||||
set(storage_v3_src_files
|
||||
commit_log.cpp
|
||||
constraints.cpp
|
||||
${lcp_storage_v3_cpp_files}
|
||||
temporal.cpp
|
||||
durability/durability.cpp
|
||||
durability/serialization.cpp
|
||||
durability/snapshot.cpp
|
||||
durability/wal.cpp
|
||||
edge_accessor.cpp
|
||||
indices.cpp
|
||||
key_store.cpp
|
||||
lexicographically_ordered_vertex.cpp
|
||||
property_store.cpp
|
||||
vertex_accessor.cpp
|
||||
schemas.cpp
|
||||
schema_validator.cpp
|
||||
shard.cpp
|
||||
bindings/typed_value.cpp
|
||||
shard_rsm.cpp
|
||||
storage.cpp)
|
||||
|
||||
# #### Replication #####
|
||||
define_add_lcp(add_lcp_storage lcp_storage_cpp_files generated_lcp_storage_files)
|
||||
|
||||
add_lcp_storage(replication/rpc.lcp SLK_SERIALIZE)
|
||||
|
||||
add_custom_target(generate_lcp_storage_v3 DEPENDS ${generated_lcp_storage_files})
|
||||
|
||||
set(storage_v3_src_files
|
||||
${storage_v3_src_files}
|
||||
replication/replication_client.cpp
|
||||
replication/replication_server.cpp
|
||||
replication/serialization.cpp
|
||||
replication/slk.cpp
|
||||
${lcp_storage_cpp_files})
|
||||
|
||||
# ######################
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(mg-storage-v3 STATIC ${storage_v3_src_files})
|
||||
add_dependencies(mg-storage-v3 generate_lcp_ast_storage_v3)
|
||||
target_link_libraries(mg-storage-v3 Threads::Threads mg-utils gflags)
|
||||
target_include_directories(mg-storage-v3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
|
||||
|
||||
add_dependencies(mg-storage-v3 generate_lcp_storage)
|
||||
target_link_libraries(mg-storage-v3 mg-rpc mg-slk mg-expr)
|
||||
target_link_libraries(mg-storage-v3 mg-slk mg-expr mg-io)
|
||||
|
2724
src/storage/v3/bindings/ast/ast.lcp
Normal file
2724
src/storage/v3/bindings/ast/ast.lcp
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,10 +10,7 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace memgraph::storage::v3::replication {
|
||||
enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY, INVALID };
|
||||
} // namespace memgraph::storage::v3::replication
|
||||
#include "expr/ast/ast_visitor.hpp"
|
15
src/storage/v3/bindings/bindings.hpp
Normal file
15
src/storage/v3/bindings/bindings.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
// 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
|
||||
|
||||
#define MG_AST_INCLUDE_PATH "storage/v3/bindings/ast/ast.hpp" // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#define MG_INJECTED_NAMESPACE_NAME memgraph::storage::v3 // NOLINT(cppcoreguidelines-macro-usage)
|
20
src/storage/v3/bindings/cypher_main_visitor.hpp
Normal file
20
src/storage/v3/bindings/cypher_main_visitor.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/ast/cypher_main_visitor.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using CypherMainVisitor = memgraph::expr::CypherMainVisitor;
|
||||
} // namespace memgraph::storage::v3
|
203
src/storage/v3/bindings/db_accessor.hpp
Normal file
203
src/storage/v3/bindings/db_accessor.hpp
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/shard.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class DbAccessor final {
|
||||
Shard::Accessor *accessor_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::v3::VerticesIterable iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::v3::VerticesIterable::Iterator it_;
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::v3::VerticesIterable::Iterator it) : it_(it) {}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::v3::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
|
||||
Iterator begin() { return Iterator(iterable_.begin()); }
|
||||
|
||||
Iterator end() { return Iterator(iterable_.end()); }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
std::optional<VertexAccessor> FindVertex(uint64_t /*unused*/) { return std::nullopt; }
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(primary_key, view);
|
||||
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
|
||||
return VerticesIterable(accessor_->Vertices(label, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, value, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
|
||||
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
|
||||
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
|
||||
auto maybe_vertex_acc = accessor_->CreateVertexAndValidate(primary_label, labels, properties);
|
||||
if (maybe_vertex_acc.HasError()) {
|
||||
return {std::move(maybe_vertex_acc.GetError())};
|
||||
}
|
||||
return VertexAccessor{maybe_vertex_acc.GetValue()};
|
||||
}
|
||||
|
||||
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::v3::EdgeTypeId &edge_type) {
|
||||
static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0);
|
||||
auto maybe_edge = accessor_->CreateEdge(from->Id(storage::v3::View::NEW).GetValue(),
|
||||
to->Id(storage::v3::View::NEW).GetValue(), edge_type, kDummyGid);
|
||||
if (maybe_edge.HasError()) return {maybe_edge.GetError()};
|
||||
return EdgeAccessor(*maybe_edge);
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
||||
auto res = accessor_->DeleteEdge(edge->FromVertex(), edge->ToVertex(), edge->Gid());
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<EdgeAccessor>{};
|
||||
}
|
||||
|
||||
return std::make_optional<EdgeAccessor>(*value);
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||
VertexAccessor *vertex_accessor) {
|
||||
using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>;
|
||||
|
||||
auto res = accessor_->DetachDeleteVertex(vertex_accessor);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<ReturnType>{};
|
||||
}
|
||||
|
||||
const auto &[vertex, edges] = *value;
|
||||
|
||||
std::vector<EdgeAccessor> deleted_edges;
|
||||
deleted_edges.reserve(edges.size());
|
||||
std::transform(edges.begin(), edges.end(), std::back_inserter(deleted_edges),
|
||||
[](const auto &deleted_edge) { return EdgeAccessor{deleted_edge}; });
|
||||
|
||||
return std::make_optional<ReturnType>(vertex, std::move(deleted_edges));
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<VertexAccessor>> RemoveVertex(VertexAccessor *vertex_accessor) {
|
||||
auto res = accessor_->DeleteVertex(vertex_accessor);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<VertexAccessor>{};
|
||||
}
|
||||
|
||||
return {std::make_optional<VertexAccessor>(*value)};
|
||||
}
|
||||
|
||||
storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
|
||||
|
||||
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }
|
||||
|
||||
const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); }
|
||||
|
||||
const std::string &LabelToName(storage::v3::LabelId label) const { return accessor_->LabelToName(label); }
|
||||
|
||||
const std::string &EdgeTypeToName(storage::v3::EdgeTypeId type) const { return accessor_->EdgeTypeToName(type); }
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
bool LabelIndexExists(storage::v3::LabelId label) const { return accessor_->LabelIndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(storage::v3::LabelId label, storage::v3::PropertyId prop) const {
|
||||
return accessor_->LabelPropertyIndexExists(label, prop);
|
||||
}
|
||||
|
||||
int64_t VerticesCount() const { return accessor_->ApproximateVertexCount(); }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label) const { return accessor_->ApproximateVertexCount(label); }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property) const {
|
||||
return accessor_->ApproximateVertexCount(label, property);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const storage::v3::PropertyValue &value) const {
|
||||
return accessor_->ApproximateVertexCount(label, property, value);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) const {
|
||||
return accessor_->ApproximateVertexCount(label, property, lower, upper);
|
||||
}
|
||||
|
||||
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
|
||||
|
||||
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
|
||||
|
||||
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
|
||||
};
|
||||
} // namespace memgraph::storage::v3
|
104
src/storage/v3/bindings/eval.hpp
Normal file
104
src/storage/v3/bindings/eval.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/interpret/eval.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct PropertyToTypedValueConverter {
|
||||
TypedValue operator()(const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); }
|
||||
|
||||
TypedValue operator()(const auto &val, utils::MemoryResource *mem) {
|
||||
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val, mem);
|
||||
}
|
||||
};
|
||||
|
||||
struct Parameters {
|
||||
public:
|
||||
/**
|
||||
* Adds a value to the stripped arguments under a token position.
|
||||
*
|
||||
* @param position Token position in query of value.
|
||||
* @param value
|
||||
*/
|
||||
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace_back(position, value); }
|
||||
|
||||
/**
|
||||
* Returns the value found for the given token position.
|
||||
*
|
||||
* @param position Token position in query of value.
|
||||
* @return Value for the given token position.
|
||||
*/
|
||||
const storage::v3::PropertyValue &AtTokenPosition(int position) const {
|
||||
auto found = std::find_if(storage_.begin(), storage_.end(), [&](const auto &a) { return a.first == position; });
|
||||
MG_ASSERT(found != storage_.end(), "Token position must be present in container");
|
||||
return found->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position-th stripped value. Asserts that this
|
||||
* container has at least (position + 1) elements.
|
||||
*
|
||||
* @param position Which stripped param is sought.
|
||||
* @return Token position and value for sought param.
|
||||
*/
|
||||
const std::pair<int, storage::v3::PropertyValue> &At(int position) const {
|
||||
MG_ASSERT(position < static_cast<int>(storage_.size()), "Invalid position");
|
||||
return storage_[position];
|
||||
}
|
||||
|
||||
/** Returns the number of arguments in this container */
|
||||
auto size() const { return storage_.size(); }
|
||||
|
||||
auto begin() const { return storage_.begin(); }
|
||||
auto end() const { return storage_.end(); }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<int, storage::v3::PropertyValue>> storage_;
|
||||
};
|
||||
|
||||
struct EvaluationContext {
|
||||
/// Memory for allocations during evaluation of a *single* Pull call.
|
||||
///
|
||||
/// Although the assigned memory may live longer than the duration of a Pull
|
||||
/// (e.g. memory is the same as the whole execution memory), you have to treat
|
||||
/// it as if the lifetime is only valid during the Pull.
|
||||
utils::MemoryResource *memory{utils::NewDeleteResource()};
|
||||
int64_t timestamp{-1};
|
||||
Parameters parameters;
|
||||
/// All properties indexable via PropertyIx
|
||||
std::vector<storage::v3::PropertyId> properties;
|
||||
/// All labels indexable via LabelIx
|
||||
std::vector<storage::v3::LabelId> labels;
|
||||
/// All counters generated by `counter` function, mutable because the function
|
||||
/// modifies the values
|
||||
mutable std::unordered_map<std::string, int64_t> counters;
|
||||
};
|
||||
|
||||
using ExpressionEvaluator =
|
||||
memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View,
|
||||
storage::v3::LabelId, storage::v3::PropertyStore, PropertyToTypedValueConverter,
|
||||
memgraph::storage::v3::Error>;
|
||||
|
||||
} // namespace memgraph::storage::v3
|
21
src/storage/v3/bindings/frame.hpp
Normal file
21
src/storage/v3/bindings/frame.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/interpret/frame.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using Frame = memgraph::expr::Frame<TypedValue>;
|
||||
} // namespace memgraph::storage::v3
|
18
src/storage/v3/bindings/pretty_print.hpp
Normal file
18
src/storage/v3/bindings/pretty_print.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/ast/pretty_print.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {} // namespace memgraph::storage::v3
|
20
src/storage/v3/bindings/symbol.hpp
Normal file
20
src/storage/v3/bindings/symbol.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using Symbol = memgraph::expr::Symbol;
|
||||
} // namespace memgraph::storage::v3
|
16
src/storage/v3/bindings/symbol_generator.hpp
Normal file
16
src/storage/v3/bindings/symbol_generator.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol_generator.hpp"
|
20
src/storage/v3/bindings/symbol_table.hpp
Normal file
20
src/storage/v3/bindings/symbol_table.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol_table.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using SymbolTable = memgraph::expr::SymbolTable;
|
||||
} // namespace memgraph::storage::v3
|
23
src/storage/v3/bindings/typed_value.cpp
Normal file
23
src/storage/v3/bindings/typed_value.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "expr/typed_value.hpp"
|
||||
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/path.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
|
||||
namespace v3 = memgraph::storage::v3;
|
||||
template class TypedValueT<v3::VertexAccessor, v3::EdgeAccessor, v3::Path>;
|
||||
|
||||
} // namespace memgraph::expr
|
28
src/storage/v3/bindings/typed_value.hpp
Normal file
28
src/storage/v3/bindings/typed_value.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 "expr/typed_value.hpp"
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/path.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace v3 = memgraph::storage::v3;
|
||||
extern template class memgraph::expr::TypedValueT<v3::VertexAccessor, v3::EdgeAccessor, v3::Path>;
|
||||
} // namespace memgraph::expr
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using TypedValue = memgraph::expr::TypedValueT<VertexAccessor, EdgeAccessor, Path>;
|
||||
} // namespace memgraph::storage::v3
|
@ -1,113 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/commit_log.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
CommitLog::CommitLog() : allocator_(utils::NewDeleteResource()) {}
|
||||
|
||||
CommitLog::CommitLog(uint64_t oldest_active)
|
||||
: head_start_{oldest_active / kIdsInBlock * kIdsInBlock},
|
||||
next_start_{head_start_ + kIdsInBlock},
|
||||
allocator_{utils::NewDeleteResource()} {
|
||||
head_ = allocator_.allocate(1);
|
||||
allocator_.construct(head_);
|
||||
|
||||
// set all the previous ids
|
||||
const auto field_idx = (oldest_active % kIdsInBlock) / kIdsInField;
|
||||
for (size_t i = 0; i < field_idx; ++i) {
|
||||
head_->field[i] = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
const auto idx_in_field = oldest_active % kIdsInField;
|
||||
if (idx_in_field != 0) {
|
||||
head_->field[field_idx] = std::numeric_limits<uint64_t>::max();
|
||||
head_->field[field_idx] >>= kIdsInField - idx_in_field;
|
||||
}
|
||||
|
||||
oldest_active_ = oldest_active;
|
||||
}
|
||||
|
||||
CommitLog::~CommitLog() {
|
||||
while (head_) {
|
||||
Block *tmp = head_->next;
|
||||
head_->~Block();
|
||||
allocator_.deallocate(head_, 1);
|
||||
head_ = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void CommitLog::MarkFinished(uint64_t id) {
|
||||
std::lock_guard<utils::SpinLock> guard(lock_);
|
||||
|
||||
Block *block = FindOrCreateBlock(id);
|
||||
block->field[(id % kIdsInBlock) / kIdsInField] |= 1ULL << (id % kIdsInField);
|
||||
if (id == oldest_active_) {
|
||||
UpdateOldestActive();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t CommitLog::OldestActive() {
|
||||
std::lock_guard<utils::SpinLock> guard(lock_);
|
||||
return oldest_active_;
|
||||
}
|
||||
|
||||
void CommitLog::UpdateOldestActive() {
|
||||
while (head_) {
|
||||
// This is necessary for amortized constant complexity. If we always start
|
||||
// from the 0th field, the amount of steps we make through each block is
|
||||
// quadratic in kBlockSize.
|
||||
uint64_t start_field = oldest_active_ >= head_start_ ? (oldest_active_ - head_start_) / kIdsInField : 0;
|
||||
for (uint64_t i = start_field; i < kBlockSize; ++i) {
|
||||
if (head_->field[i] != std::numeric_limits<uint64_t>::max()) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions)
|
||||
oldest_active_ = head_start_ + i * kIdsInField + __builtin_ffsl(static_cast<int64_t>(~head_->field[i])) - 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// All IDs in this block are marked, we can delete it now.
|
||||
Block *tmp = head_->next;
|
||||
head_->~Block();
|
||||
allocator_.deallocate(head_, 1);
|
||||
head_ = tmp;
|
||||
head_start_ += kIdsInBlock;
|
||||
}
|
||||
|
||||
oldest_active_ = next_start_;
|
||||
}
|
||||
|
||||
CommitLog::Block *CommitLog::FindOrCreateBlock(const uint64_t id) {
|
||||
if (!head_) {
|
||||
head_ = allocator_.allocate(1);
|
||||
allocator_.construct(head_);
|
||||
head_start_ = next_start_;
|
||||
next_start_ += kIdsInBlock;
|
||||
}
|
||||
|
||||
Block *current = head_;
|
||||
uint64_t current_start = head_start_;
|
||||
|
||||
while (id >= current_start + kIdsInBlock) {
|
||||
if (!current->next) {
|
||||
current->next = allocator_.allocate(1);
|
||||
allocator_.construct(current->next);
|
||||
next_start_ += kIdsInBlock;
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
current_start += kIdsInBlock;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
} // namespace memgraph::storage::v3
|
@ -1,79 +0,0 @@
|
||||
// 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.
|
||||
|
||||
/// @file commit_log.hpp
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
/// This class keeps track of finalized transactions to provide info on the
|
||||
/// oldest active transaction (minimal transaction ID which could still be
|
||||
/// active).
|
||||
///
|
||||
/// Basically, it is a set which, at the beginning, contains all transaction
|
||||
/// IDs and supports two operations: remove an ID from the set (\ref
|
||||
/// SetFinished) and retrieve the minimal ID still in the set (\ref
|
||||
/// OldestActive).
|
||||
///
|
||||
/// This class is thread-safe.
|
||||
class CommitLog final {
|
||||
public:
|
||||
// TODO(mtomic): use pool allocator for blocks
|
||||
CommitLog();
|
||||
/// Create a commit log which has the oldest active id set to
|
||||
/// oldest_active
|
||||
/// @param oldest_active the oldest active id
|
||||
explicit CommitLog(uint64_t oldest_active);
|
||||
|
||||
CommitLog(const CommitLog &) = delete;
|
||||
CommitLog &operator=(const CommitLog &) = delete;
|
||||
CommitLog(CommitLog &&) = delete;
|
||||
CommitLog &operator=(CommitLog &&) = delete;
|
||||
|
||||
~CommitLog();
|
||||
|
||||
/// Mark a transaction as finished.
|
||||
/// @throw std::bad_alloc
|
||||
void MarkFinished(uint64_t id);
|
||||
|
||||
/// Retrieve the oldest transaction still not marked as finished.
|
||||
uint64_t OldestActive();
|
||||
|
||||
private:
|
||||
static constexpr uint64_t kBlockSize = 8192;
|
||||
static constexpr uint64_t kIdsInField = sizeof(uint64_t) * 8;
|
||||
static constexpr uint64_t kIdsInBlock = kBlockSize * kIdsInField;
|
||||
|
||||
struct Block {
|
||||
Block *next{nullptr};
|
||||
uint64_t field[kBlockSize]{};
|
||||
};
|
||||
|
||||
void UpdateOldestActive();
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Block *FindOrCreateBlock(uint64_t id);
|
||||
|
||||
Block *head_{nullptr};
|
||||
uint64_t head_start_{0};
|
||||
uint64_t next_start_{0};
|
||||
uint64_t oldest_active_{0};
|
||||
utils::SpinLock lock_;
|
||||
utils::Allocator<Block> allocator_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -14,6 +14,9 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
#include "io/time.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
|
||||
@ -23,35 +26,14 @@ namespace memgraph::storage::v3 {
|
||||
/// the storage. This class also defines the default behavior.
|
||||
struct Config {
|
||||
struct Gc {
|
||||
enum class Type { NONE, PERIODIC };
|
||||
|
||||
Type type{Type::PERIODIC};
|
||||
std::chrono::milliseconds interval{std::chrono::milliseconds(1000)};
|
||||
// Interval after which the committed deltas are cleaned up
|
||||
io::Duration reclamation_interval{};
|
||||
} gc;
|
||||
|
||||
struct Items {
|
||||
bool properties_on_edges{true};
|
||||
} items;
|
||||
|
||||
struct Durability {
|
||||
enum class SnapshotWalMode { DISABLED, PERIODIC_SNAPSHOT, PERIODIC_SNAPSHOT_WITH_WAL };
|
||||
|
||||
std::filesystem::path storage_directory{"storage"};
|
||||
|
||||
bool recover_on_startup{false};
|
||||
|
||||
SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED};
|
||||
|
||||
std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)};
|
||||
uint64_t snapshot_retention_count{3};
|
||||
|
||||
uint64_t wal_file_size_kibibytes{static_cast<uint64_t>(20 * 1024)};
|
||||
uint64_t wal_file_flush_every_n_tx{100000};
|
||||
|
||||
bool snapshot_on_exit{false};
|
||||
|
||||
} durability;
|
||||
|
||||
struct Transaction {
|
||||
IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
} transaction;
|
||||
|
@ -1,415 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/constraints.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
namespace {
|
||||
|
||||
/// Helper function that determines position of the given `property` in the
|
||||
/// sorted `property_array` using binary search. In the case that `property`
|
||||
/// cannot be found, `std::nullopt` is returned.
|
||||
std::optional<size_t> FindPropertyPosition(const PropertyIdArray &property_array, PropertyId property) {
|
||||
const auto *it = std::lower_bound(property_array.values, property_array.values + property_array.size, property);
|
||||
if (it == property_array.values + property_array.size || *it != property) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return it - property_array.values;
|
||||
}
|
||||
|
||||
/// Helper function for validating unique constraints on commit. Returns true if
|
||||
/// the last committed version of the given vertex contains the given label and
|
||||
/// set of property values. This function should be called when commit lock is
|
||||
/// active.
|
||||
bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
|
||||
const std::vector<PropertyValue> &value_array, const Transaction &transaction,
|
||||
uint64_t commit_timestamp) {
|
||||
MG_ASSERT(properties.size() == value_array.size(), "Invalid database state!");
|
||||
|
||||
PropertyIdArray property_array(properties.size());
|
||||
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
|
||||
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
|
||||
|
||||
// Since the commit lock is active, any transaction that tries to write to
|
||||
// a vertex which is part of the given `transaction` will result in a
|
||||
// serialization error. But, note that the given `vertex`'s data does not have
|
||||
// to be modified in the current `transaction`, meaning that a guard lock to
|
||||
// access vertex's data is still necessary because another active transaction
|
||||
// could modify it in the meantime.
|
||||
Delta *delta{nullptr};
|
||||
bool deleted{false};
|
||||
bool has_label{false};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
delta = vertex.delta;
|
||||
deleted = vertex.deleted;
|
||||
has_label = VertexHasLabel(vertex, label);
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &property : properties) {
|
||||
current_value_equal_to_value[i] = vertex.properties.IsPropertyEqual(property, value_array[i]);
|
||||
property_array.values[i] = property;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
while (delta != nullptr) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
if (ts < commit_timestamp || ts == transaction.transaction_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (delta->action) {
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
auto pos = FindPropertyPosition(property_array, delta->property.key);
|
||||
if (pos) {
|
||||
current_value_equal_to_value[*pos] = delta->property.value == value_array[*pos];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
MG_ASSERT(deleted, "Invalid database state!");
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_LABEL: {
|
||||
if (delta->label == label) {
|
||||
MG_ASSERT(!has_label, "Invalid database state!");
|
||||
has_label = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
case Delta::Action::REMOVE_LABEL: {
|
||||
if (delta->label == label) {
|
||||
MG_ASSERT(has_label, "Invalid database state!");
|
||||
has_label = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
|
||||
delta = delta->next.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < properties.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !deleted && has_label;
|
||||
}
|
||||
|
||||
/// Helper function for unique constraint garbage collection. Returns true if
|
||||
/// there's a reachable version of the vertex that has the given label and
|
||||
/// property values.
|
||||
bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
|
||||
const std::vector<PropertyValue> &values, uint64_t timestamp) {
|
||||
MG_ASSERT(properties.size() == values.size(), "Invalid database state!");
|
||||
|
||||
PropertyIdArray property_array(properties.size());
|
||||
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
|
||||
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
|
||||
|
||||
bool has_label{false};
|
||||
bool deleted{false};
|
||||
Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
has_label = VertexHasLabel(vertex, label);
|
||||
deleted = vertex.deleted;
|
||||
delta = vertex.delta;
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &property : properties) {
|
||||
current_value_equal_to_value[i] = vertex.properties.IsPropertyEqual(property, values[i]);
|
||||
property_array.values[i] = property;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool all_values_match = true;
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
all_values_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deleted && has_label && all_values_match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (delta != nullptr) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
if (ts < timestamp) {
|
||||
break;
|
||||
}
|
||||
switch (delta->action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
if (delta->label == label) {
|
||||
MG_ASSERT(!has_label, "Invalid database state!");
|
||||
has_label = true;
|
||||
}
|
||||
break;
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
if (delta->label == label) {
|
||||
MG_ASSERT(has_label, "Invalid database state!");
|
||||
has_label = false;
|
||||
}
|
||||
break;
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
auto pos = FindPropertyPosition(property_array, delta->property.key);
|
||||
if (pos) {
|
||||
current_value_equal_to_value[*pos] = delta->property.value == values[*pos];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
MG_ASSERT(deleted, "Invalid database state!");
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
|
||||
bool all_values_match = true;
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
all_values_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deleted && has_label && all_values_match) {
|
||||
return true;
|
||||
}
|
||||
delta = delta->next.load(std::memory_order_acquire);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Helper function that, given the set of `properties`, extracts corresponding
|
||||
/// property values from the `vertex`.
|
||||
/// @throw std::bad_alloc
|
||||
std::optional<std::vector<PropertyValue>> ExtractPropertyValues(const Vertex &vertex,
|
||||
const std::set<PropertyId> &properties) {
|
||||
std::vector<PropertyValue> value_array;
|
||||
value_array.reserve(properties.size());
|
||||
for (const auto &prop : properties) {
|
||||
auto value = vertex.properties.GetProperty(prop);
|
||||
if (value.IsNull()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value_array.emplace_back(std::move(value));
|
||||
}
|
||||
return std::move(value_array);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs) {
|
||||
return lhs.type == rhs.type && lhs.label == rhs.label && lhs.properties == rhs.properties;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator<(const Entry &rhs) const {
|
||||
if (values < rhs.values) {
|
||||
return true;
|
||||
}
|
||||
if (rhs.values < values) {
|
||||
return false;
|
||||
}
|
||||
return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp);
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator==(const Entry &rhs) const {
|
||||
return values == rhs.values && vertex == rhs.vertex && timestamp == rhs.timestamp;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator<(const std::vector<PropertyValue> &rhs) const { return values < rhs; }
|
||||
|
||||
bool UniqueConstraints::Entry::operator==(const std::vector<PropertyValue> &rhs) const { return values == rhs; }
|
||||
|
||||
void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx) {
|
||||
for (auto &[label_props, storage] : constraints_) {
|
||||
if (!VertexHasLabel(*vertex, label_props.first)) {
|
||||
continue;
|
||||
}
|
||||
auto values = ExtractPropertyValues(*vertex, label_props.second);
|
||||
if (values) {
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{std::move(*values), vertex, tx.start_timestamp});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> UniqueConstraints::CreateConstraint(
|
||||
LabelId label, const std::set<PropertyId> &properties, utils::SkipList<Vertex>::Accessor vertices) {
|
||||
if (properties.empty()) {
|
||||
return CreationStatus::EMPTY_PROPERTIES;
|
||||
}
|
||||
if (properties.size() > kUniqueConstraintsMaxProperties) {
|
||||
return CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
auto [constraint, emplaced] =
|
||||
constraints_.emplace(std::piecewise_construct, std::forward_as_tuple(label, properties), std::forward_as_tuple());
|
||||
|
||||
if (!emplaced) {
|
||||
// Constraint already exists.
|
||||
return CreationStatus::ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
bool violation_found = false;
|
||||
|
||||
{
|
||||
auto acc = constraint->second.access();
|
||||
|
||||
for (const Vertex &vertex : vertices) {
|
||||
if (vertex.deleted || !VertexHasLabel(vertex, label)) {
|
||||
continue;
|
||||
}
|
||||
auto values = ExtractPropertyValues(vertex, properties);
|
||||
if (!values) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check whether there already is a vertex with the same values for the
|
||||
// given label and property.
|
||||
auto it = acc.find_equal_or_greater(*values);
|
||||
if (it != acc.end() && it->values == *values) {
|
||||
violation_found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
acc.insert(Entry{std::move(*values), &vertex, 0});
|
||||
}
|
||||
}
|
||||
|
||||
if (violation_found) {
|
||||
// In the case of the violation, storage for the current constraint has to
|
||||
// be removed.
|
||||
constraints_.erase(constraint);
|
||||
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label, properties};
|
||||
}
|
||||
return CreationStatus::SUCCESS;
|
||||
}
|
||||
|
||||
UniqueConstraints::DeletionStatus UniqueConstraints::DropConstraint(LabelId label,
|
||||
const std::set<PropertyId> &properties) {
|
||||
if (properties.empty()) {
|
||||
return UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES;
|
||||
}
|
||||
if (properties.size() > kUniqueConstraintsMaxProperties) {
|
||||
return UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED;
|
||||
}
|
||||
if (constraints_.erase({label, properties}) > 0) {
|
||||
return UniqueConstraints::DeletionStatus::SUCCESS;
|
||||
}
|
||||
return UniqueConstraints::DeletionStatus::NOT_FOUND;
|
||||
}
|
||||
|
||||
std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &vertex, const Transaction &tx,
|
||||
uint64_t commit_timestamp) const {
|
||||
if (vertex.deleted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (const auto &[label_props, storage] : constraints_) {
|
||||
const auto &label = label_props.first;
|
||||
const auto &properties = label_props.second;
|
||||
if (!VertexHasLabel(vertex, label)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value_array = ExtractPropertyValues(vertex, properties);
|
||||
if (!value_array) {
|
||||
continue;
|
||||
}
|
||||
auto acc = storage.access();
|
||||
auto it = acc.find_equal_or_greater(*value_array);
|
||||
for (; it != acc.end(); ++it) {
|
||||
if (*value_array < it->values) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The `vertex` that is going to be committed violates a unique constraint
|
||||
// if it's different than a vertex indexed in the list of constraints and
|
||||
// has the same label and property value as the last committed version of
|
||||
// the vertex from the list.
|
||||
if (&vertex != it->vertex &&
|
||||
LastCommittedVersionHasLabelProperty(*it->vertex, label, properties, *value_array, tx, commit_timestamp)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label, properties};
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> UniqueConstraints::ListConstraints() const {
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> ret;
|
||||
ret.reserve(constraints_.size());
|
||||
for (const auto &[label_props, _] : constraints_) {
|
||||
ret.push_back(label_props);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void UniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||
for (auto &[label_props, storage] : constraints_) {
|
||||
auto acc = storage.access();
|
||||
for (auto it = acc.begin(); it != acc.end();) {
|
||||
auto next_it = it;
|
||||
++next_it;
|
||||
|
||||
if (it->timestamp >= oldest_active_start_timestamp) {
|
||||
it = next_it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((next_it != acc.end() && it->vertex == next_it->vertex && it->values == next_it->values) ||
|
||||
!AnyVersionHasLabelProperty(*it->vertex, label_props.first, label_props.second, it->values,
|
||||
oldest_active_start_timestamp)) {
|
||||
acc.remove(*it);
|
||||
}
|
||||
it = next_it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -1,199 +0,0 @@
|
||||
// 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 <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// NOLINTNEXTLINE(misc-definitions-in-headers)
|
||||
const size_t kUniqueConstraintsMaxProperties = 32;
|
||||
|
||||
/// Utility class to store data in a fixed size array. The array is used
|
||||
/// instead of `std::vector` to avoid `std::bad_alloc` exception where not
|
||||
/// necessary.
|
||||
template <class T>
|
||||
struct FixedCapacityArray {
|
||||
size_t size;
|
||||
T values[kUniqueConstraintsMaxProperties];
|
||||
|
||||
explicit FixedCapacityArray(size_t array_size) : size(array_size) {
|
||||
MG_ASSERT(size <= kUniqueConstraintsMaxProperties, "Invalid array size!");
|
||||
}
|
||||
};
|
||||
|
||||
using PropertyIdArray = FixedCapacityArray<PropertyId>;
|
||||
|
||||
struct ConstraintViolation {
|
||||
enum class Type {
|
||||
EXISTENCE,
|
||||
UNIQUE,
|
||||
};
|
||||
|
||||
Type type;
|
||||
LabelId label;
|
||||
|
||||
// While multiple properties are supported by unique constraints, the
|
||||
// `properties` set will always have exactly one element in the case of
|
||||
// existence constraint violation.
|
||||
std::set<PropertyId> properties;
|
||||
};
|
||||
|
||||
bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs);
|
||||
|
||||
class UniqueConstraints {
|
||||
private:
|
||||
struct Entry {
|
||||
std::vector<PropertyValue> values;
|
||||
const Vertex *vertex;
|
||||
uint64_t timestamp;
|
||||
|
||||
bool operator<(const Entry &rhs) const;
|
||||
bool operator==(const Entry &rhs) const;
|
||||
|
||||
bool operator<(const std::vector<PropertyValue> &rhs) const;
|
||||
bool operator==(const std::vector<PropertyValue> &rhs) const;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Status for creation of unique constraints.
|
||||
/// Note that this does not cover the case when the constraint is violated.
|
||||
enum class CreationStatus {
|
||||
SUCCESS,
|
||||
ALREADY_EXISTS,
|
||||
EMPTY_PROPERTIES,
|
||||
PROPERTIES_SIZE_LIMIT_EXCEEDED,
|
||||
};
|
||||
|
||||
/// Status for deletion of unique constraints.
|
||||
enum class DeletionStatus {
|
||||
SUCCESS,
|
||||
NOT_FOUND,
|
||||
EMPTY_PROPERTIES,
|
||||
PROPERTIES_SIZE_LIMIT_EXCEEDED,
|
||||
};
|
||||
|
||||
/// Indexes the given vertex for relevant labels and properties.
|
||||
/// This method should be called before committing and validating vertices
|
||||
/// against unique constraints.
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx);
|
||||
|
||||
/// Creates unique constraint on the given `label` and a list of `properties`.
|
||||
/// Returns constraint violation if there are multiple vertices with the same
|
||||
/// label and property values. Returns `CreationStatus::ALREADY_EXISTS` if
|
||||
/// constraint already existed, `CreationStatus::EMPTY_PROPERTIES` if the
|
||||
/// given list of properties is empty,
|
||||
/// `CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the list of properties
|
||||
/// exceeds the maximum allowed number of properties, and
|
||||
/// `CreationStatus::SUCCESS` on success.
|
||||
/// @throw std::bad_alloc
|
||||
utils::BasicResult<ConstraintViolation, CreationStatus> CreateConstraint(LabelId label,
|
||||
const std::set<PropertyId> &properties,
|
||||
utils::SkipList<Vertex>::Accessor vertices);
|
||||
|
||||
/// Deletes the specified constraint. Returns `DeletionStatus::NOT_FOUND` if
|
||||
/// there is not such constraint in the storage,
|
||||
/// `DeletionStatus::EMPTY_PROPERTIES` if the given set of `properties` is
|
||||
/// empty, `DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the given set
|
||||
/// of `properties` exceeds the maximum allowed number of properties, and
|
||||
/// `DeletionStatus::SUCCESS` on success.
|
||||
DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties);
|
||||
|
||||
bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) {
|
||||
return constraints_.find({label, properties}) != constraints_.end();
|
||||
}
|
||||
|
||||
/// Validates the given vertex against unique constraints before committing.
|
||||
/// This method should be called while commit lock is active with
|
||||
/// `commit_timestamp` being a potential commit timestamp of the transaction.
|
||||
/// @throw std::bad_alloc
|
||||
std::optional<ConstraintViolation> Validate(const Vertex &vertex, const Transaction &tx,
|
||||
uint64_t commit_timestamp) const;
|
||||
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const;
|
||||
|
||||
/// GC method that removes outdated entries from constraints' storages.
|
||||
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
||||
|
||||
void Clear() { constraints_.clear(); }
|
||||
|
||||
private:
|
||||
std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>> constraints_;
|
||||
};
|
||||
|
||||
struct Constraints {
|
||||
std::vector<std::pair<LabelId, PropertyId>> existence_constraints;
|
||||
UniqueConstraints unique_constraints;
|
||||
};
|
||||
|
||||
/// Adds a unique constraint to `constraints`. Returns true if the constraint
|
||||
/// was successfully added, false if it already exists and a
|
||||
/// `ConstraintViolation` if there is an existing vertex violating the
|
||||
/// constraint.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
|
||||
Constraints *constraints, LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices) {
|
||||
if (utils::Contains(constraints->existence_constraints, std::make_pair(label, property))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &vertex : vertices) {
|
||||
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
constraints->existence_constraints.emplace_back(label, property);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Removes a unique constraint from `constraints`. Returns true if the
|
||||
/// constraint was removed, and false if it doesn't exist.
|
||||
inline bool DropExistenceConstraint(Constraints *constraints, LabelId label, PropertyId property) {
|
||||
auto it = std::find(constraints->existence_constraints.begin(), constraints->existence_constraints.end(),
|
||||
std::make_pair(label, property));
|
||||
if (it == constraints->existence_constraints.end()) {
|
||||
return false;
|
||||
}
|
||||
constraints->existence_constraints.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Verifies that the given vertex satisfies all existence constraints. Returns
|
||||
/// `std::nullopt` if all checks pass, and `ConstraintViolation` describing the
|
||||
/// violated constraint otherwise.
|
||||
[[nodiscard]] inline std::optional<ConstraintViolation> ValidateExistenceConstraints(const Vertex &vertex,
|
||||
const Constraints &constraints) {
|
||||
for (const auto &[label, property] : constraints.existence_constraints) {
|
||||
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/// Returns a list of all created existence constraints.
|
||||
inline std::vector<std::pair<LabelId, PropertyId>> ListExistenceConstraints(const Constraints &constraints) {
|
||||
return constraints.existence_constraints;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "expr/typed_value.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -67,6 +68,57 @@ TTypedValue PropertyToTypedValue(const PropertyValue &value) {
|
||||
LOG_FATAL("Unsupported type");
|
||||
}
|
||||
|
||||
template <typename TTypedValue>
|
||||
TTypedValue PropertyToTypedValue(const PropertyValue &value, utils::MemoryResource *mem) {
|
||||
switch (value.type()) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
return TTypedValue(mem);
|
||||
case storage::v3::PropertyValue::Type::Bool:
|
||||
return TTypedValue(value.ValueBool(), mem);
|
||||
case storage::v3::PropertyValue::Type::Int:
|
||||
return TTypedValue(value.ValueInt(), mem);
|
||||
case storage::v3::PropertyValue::Type::Double:
|
||||
return TTypedValue(value.ValueDouble(), mem);
|
||||
case storage::v3::PropertyValue::Type::String:
|
||||
return TTypedValue(value.ValueString(), mem);
|
||||
case storage::v3::PropertyValue::Type::List: {
|
||||
const auto &src = value.ValueList();
|
||||
std::vector<TTypedValue> dst;
|
||||
dst.reserve(src.size());
|
||||
for (const auto &elem : src) {
|
||||
dst.push_back(PropertyToTypedValue<TTypedValue>(elem, mem));
|
||||
}
|
||||
return TTypedValue(std::move(dst), mem);
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Map: {
|
||||
const auto &src = value.ValueMap();
|
||||
std::map<std::string, TTypedValue> dst;
|
||||
for (const auto &elem : src) {
|
||||
dst.insert({std::string(elem.first), PropertyToTypedValue<TTypedValue>(elem.second, mem)});
|
||||
}
|
||||
return TTypedValue(std::move(dst), mem);
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::TemporalData: {
|
||||
const auto &temporal_data = value.ValueTemporalData();
|
||||
switch (temporal_data.type) {
|
||||
case storage::v3::TemporalType::Date: {
|
||||
return TTypedValue(utils::Date(temporal_data.microseconds), mem);
|
||||
}
|
||||
case storage::v3::TemporalType::LocalTime: {
|
||||
return TTypedValue(utils::LocalTime(temporal_data.microseconds), mem);
|
||||
}
|
||||
case storage::v3::TemporalType::LocalDateTime: {
|
||||
return TTypedValue(utils::LocalDateTime(temporal_data.microseconds), mem);
|
||||
}
|
||||
case storage::v3::TemporalType::Duration: {
|
||||
return TTypedValue(utils::Duration(temporal_data.microseconds), mem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_FATAL("Unsupported type");
|
||||
}
|
||||
|
||||
template <typename TTypedValue>
|
||||
storage::v3::PropertyValue TypedToPropertyValue(const TTypedValue &value) {
|
||||
switch (value.type()) {
|
||||
|
@ -11,11 +11,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <memory>
|
||||
#include "storage/v3/edge_ref.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/vertex_id.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
@ -24,6 +24,7 @@ namespace memgraph::storage::v3 {
|
||||
struct Vertex;
|
||||
struct Edge;
|
||||
struct Delta;
|
||||
struct CommitInfo;
|
||||
|
||||
// This class stores one of three pointers (`Delta`, `Vertex` and `Edge`)
|
||||
// without using additional memory for storing the type. The type is stored in
|
||||
@ -61,31 +62,30 @@ class PreviousPtr {
|
||||
Edge *edge{nullptr};
|
||||
};
|
||||
|
||||
PreviousPtr() : storage_(0) {}
|
||||
PreviousPtr() {}
|
||||
|
||||
PreviousPtr(const PreviousPtr &other) noexcept : storage_(other.storage_.load(std::memory_order_acquire)) {}
|
||||
PreviousPtr(const PreviousPtr &other) noexcept : storage_(other.storage_) {}
|
||||
PreviousPtr(PreviousPtr &&) = delete;
|
||||
PreviousPtr &operator=(const PreviousPtr &) = delete;
|
||||
PreviousPtr &operator=(PreviousPtr &&) = delete;
|
||||
~PreviousPtr() = default;
|
||||
|
||||
Pointer Get() const {
|
||||
uintptr_t value = storage_.load(std::memory_order_acquire);
|
||||
if (value == 0) {
|
||||
if (storage_ == 0) {
|
||||
return {};
|
||||
}
|
||||
uintptr_t type = value & kMask;
|
||||
uintptr_t type = storage_ & kMask;
|
||||
if (type == kDelta) {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||
return Pointer{reinterpret_cast<Delta *>(value & ~kMask)};
|
||||
return Pointer{reinterpret_cast<Delta *>(storage_ & ~kMask)};
|
||||
}
|
||||
if (type == kVertex) {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||
return Pointer{reinterpret_cast<Vertex *>(value & ~kMask)};
|
||||
return Pointer{reinterpret_cast<Vertex *>(storage_ & ~kMask)};
|
||||
}
|
||||
if (type == kEdge) {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||
return Pointer{reinterpret_cast<Edge *>(value & ~kMask)};
|
||||
return Pointer{reinterpret_cast<Edge *>(storage_ & ~kMask)};
|
||||
}
|
||||
LOG_FATAL("Invalid pointer type!");
|
||||
}
|
||||
@ -93,23 +93,23 @@ class PreviousPtr {
|
||||
void Set(Delta *delta) {
|
||||
auto value = reinterpret_cast<uintptr_t>(delta);
|
||||
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
|
||||
storage_.store(value | kDelta, std::memory_order_release);
|
||||
storage_ = value | kDelta;
|
||||
}
|
||||
|
||||
void Set(Vertex *vertex) {
|
||||
auto value = reinterpret_cast<uintptr_t>(vertex);
|
||||
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
|
||||
storage_.store(value | kVertex, std::memory_order_release);
|
||||
storage_ = value | kVertex;
|
||||
}
|
||||
|
||||
void Set(Edge *edge) {
|
||||
auto value = reinterpret_cast<uintptr_t>(edge);
|
||||
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
|
||||
storage_.store(value | kEdge, std::memory_order_release);
|
||||
storage_ = value | kEdge;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uintptr_t> storage_;
|
||||
uintptr_t storage_{0};
|
||||
};
|
||||
|
||||
inline bool operator==(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer &b) {
|
||||
@ -157,49 +157,49 @@ struct Delta {
|
||||
struct RemoveInEdgeTag {};
|
||||
struct RemoveOutEdgeTag {};
|
||||
|
||||
Delta(DeleteObjectTag /*unused*/, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
: action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {}
|
||||
Delta(DeleteObjectTag /*unused*/, CommitInfo *commit_info, uint64_t command_id)
|
||||
: action(Action::DELETE_OBJECT), commit_info(commit_info), command_id(command_id) {}
|
||||
|
||||
Delta(RecreateObjectTag /*unused*/, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
: action(Action::RECREATE_OBJECT), timestamp(timestamp), command_id(command_id) {}
|
||||
Delta(RecreateObjectTag /*unused*/, CommitInfo *commit_info, uint64_t command_id)
|
||||
: action(Action::RECREATE_OBJECT), commit_info(commit_info), command_id(command_id) {}
|
||||
|
||||
Delta(AddLabelTag /*unused*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
: action(Action::ADD_LABEL), timestamp(timestamp), command_id(command_id), label(label) {}
|
||||
Delta(AddLabelTag /*unused*/, LabelId label, CommitInfo *commit_info, uint64_t command_id)
|
||||
: action(Action::ADD_LABEL), commit_info(commit_info), command_id(command_id), label(label) {}
|
||||
|
||||
Delta(RemoveLabelTag /*unused*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
: action(Action::REMOVE_LABEL), timestamp(timestamp), command_id(command_id), label(label) {}
|
||||
Delta(RemoveLabelTag /*unused*/, LabelId label, CommitInfo *commit_info, uint64_t command_id)
|
||||
: action(Action::REMOVE_LABEL), commit_info(commit_info), command_id(command_id), label(label) {}
|
||||
|
||||
Delta(SetPropertyTag /*unused*/, PropertyId key, const PropertyValue &value, std::atomic<uint64_t> *timestamp,
|
||||
Delta(SetPropertyTag /*unused*/, PropertyId key, const PropertyValue &value, CommitInfo *commit_info,
|
||||
uint64_t command_id)
|
||||
: action(Action::SET_PROPERTY), timestamp(timestamp), command_id(command_id), property({key, value}) {}
|
||||
: action(Action::SET_PROPERTY), commit_info(commit_info), command_id(command_id), property({key, value}) {}
|
||||
|
||||
Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp,
|
||||
Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
|
||||
uint64_t command_id)
|
||||
: action(Action::ADD_IN_EDGE),
|
||||
timestamp(timestamp),
|
||||
commit_info(commit_info),
|
||||
command_id(command_id),
|
||||
vertex_edge({edge_type, vertex, edge}) {}
|
||||
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
|
||||
|
||||
Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp,
|
||||
Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
|
||||
uint64_t command_id)
|
||||
: action(Action::ADD_OUT_EDGE),
|
||||
timestamp(timestamp),
|
||||
commit_info(commit_info),
|
||||
command_id(command_id),
|
||||
vertex_edge({edge_type, vertex, edge}) {}
|
||||
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
|
||||
|
||||
Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge,
|
||||
std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
|
||||
uint64_t command_id)
|
||||
: action(Action::REMOVE_IN_EDGE),
|
||||
timestamp(timestamp),
|
||||
commit_info(commit_info),
|
||||
command_id(command_id),
|
||||
vertex_edge({edge_type, vertex, edge}) {}
|
||||
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
|
||||
|
||||
Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge,
|
||||
std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
|
||||
uint64_t command_id)
|
||||
: action(Action::REMOVE_OUT_EDGE),
|
||||
timestamp(timestamp),
|
||||
commit_info(commit_info),
|
||||
command_id(command_id),
|
||||
vertex_edge({edge_type, vertex, edge}) {}
|
||||
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
|
||||
|
||||
Delta(const Delta &) = delete;
|
||||
Delta(Delta &&) = delete;
|
||||
@ -212,10 +212,12 @@ struct Delta {
|
||||
case Action::RECREATE_OBJECT:
|
||||
case Action::ADD_LABEL:
|
||||
case Action::REMOVE_LABEL:
|
||||
break;
|
||||
case Action::ADD_IN_EDGE:
|
||||
case Action::ADD_OUT_EDGE:
|
||||
case Action::REMOVE_IN_EDGE:
|
||||
case Action::REMOVE_OUT_EDGE:
|
||||
std::destroy_at(&vertex_edge.vertex_id);
|
||||
break;
|
||||
case Action::SET_PROPERTY:
|
||||
property.value.~PropertyValue();
|
||||
@ -226,10 +228,10 @@ struct Delta {
|
||||
Action action;
|
||||
|
||||
// TODO: optimize with in-place copy
|
||||
std::atomic<uint64_t> *timestamp;
|
||||
CommitInfo *commit_info;
|
||||
uint64_t command_id;
|
||||
PreviousPtr prev;
|
||||
std::atomic<Delta *> next{nullptr};
|
||||
Delta *next{nullptr};
|
||||
|
||||
union {
|
||||
LabelId label;
|
||||
@ -239,7 +241,7 @@ struct Delta {
|
||||
} property;
|
||||
struct {
|
||||
EdgeTypeId edge_type;
|
||||
Vertex *vertex;
|
||||
VertexId vertex_id;
|
||||
EdgeRef edge;
|
||||
} vertex_edge;
|
||||
};
|
||||
|
@ -1,347 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/durability/durability.hpp"
|
||||
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/durability/paths.hpp"
|
||||
#include "storage/v3/durability/snapshot.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/message.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
void VerifyStorageDirectoryOwnerAndProcessUserOrDie(const std::filesystem::path &storage_directory) {
|
||||
// Get the process user ID.
|
||||
auto process_euid = geteuid();
|
||||
|
||||
// Get the data directory owner ID.
|
||||
struct stat statbuf;
|
||||
auto ret = stat(storage_directory.c_str(), &statbuf);
|
||||
if (ret != 0 && errno == ENOENT) {
|
||||
// The directory doesn't currently exist.
|
||||
return;
|
||||
}
|
||||
MG_ASSERT(ret == 0, "Couldn't get stat for '{}' because of: {} ({})", storage_directory, strerror(errno), errno);
|
||||
auto directory_owner = statbuf.st_uid;
|
||||
|
||||
auto get_username = [](auto uid) {
|
||||
auto info = getpwuid(uid);
|
||||
if (!info) return std::to_string(uid);
|
||||
return std::string(info->pw_name);
|
||||
};
|
||||
|
||||
auto user_process = get_username(process_euid);
|
||||
auto user_directory = get_username(directory_owner);
|
||||
MG_ASSERT(process_euid == directory_owner,
|
||||
"The process is running as user {}, but the data directory is "
|
||||
"owned by user {}. Please start the process as user {}!",
|
||||
user_process, user_directory, user_directory);
|
||||
}
|
||||
|
||||
std::vector<SnapshotDurabilityInfo> GetSnapshotFiles(const std::filesystem::path &snapshot_directory,
|
||||
const std::string_view uuid) {
|
||||
std::vector<SnapshotDurabilityInfo> snapshot_files;
|
||||
std::error_code error_code;
|
||||
if (utils::DirExists(snapshot_directory)) {
|
||||
for (const auto &item : std::filesystem::directory_iterator(snapshot_directory, error_code)) {
|
||||
if (!item.is_regular_file()) continue;
|
||||
try {
|
||||
auto info = ReadSnapshotInfo(item.path());
|
||||
if (uuid.empty() || info.uuid == uuid) {
|
||||
snapshot_files.emplace_back(item.path(), std::move(info.uuid), info.start_timestamp);
|
||||
}
|
||||
} catch (const RecoveryFailure &) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
|
||||
}
|
||||
|
||||
return snapshot_files;
|
||||
}
|
||||
|
||||
std::optional<std::vector<WalDurabilityInfo>> GetWalFiles(const std::filesystem::path &wal_directory,
|
||||
const std::string_view uuid,
|
||||
const std::optional<size_t> current_seq_num) {
|
||||
if (!utils::DirExists(wal_directory)) return std::nullopt;
|
||||
|
||||
std::vector<WalDurabilityInfo> wal_files;
|
||||
std::error_code error_code;
|
||||
for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) {
|
||||
if (!item.is_regular_file()) continue;
|
||||
try {
|
||||
auto info = ReadWalInfo(item.path());
|
||||
if ((uuid.empty() || info.uuid == uuid) && (!current_seq_num || info.seq_num < *current_seq_num))
|
||||
wal_files.emplace_back(info.seq_num, info.from_timestamp, info.to_timestamp, std::move(info.uuid),
|
||||
std::move(info.epoch_id), item.path());
|
||||
} catch (const RecoveryFailure &e) {
|
||||
spdlog::warn("Failed to read {}", item.path());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
|
||||
|
||||
std::sort(wal_files.begin(), wal_files.end());
|
||||
return std::move(wal_files);
|
||||
}
|
||||
|
||||
// Function used to recover all discovered indices and constraints. The
|
||||
// indices and constraints must be recovered after the data recovery is done
|
||||
// to ensure that the indices and constraints are consistent at the end of the
|
||||
// recovery process.
|
||||
void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices,
|
||||
Constraints *constraints, utils::SkipList<Vertex> *vertices) {
|
||||
spdlog::info("Recreating indices from metadata.");
|
||||
// Recover label indices.
|
||||
spdlog::info("Recreating {} label indices from metadata.", indices_constraints.indices.label.size());
|
||||
for (const auto &item : indices_constraints.indices.label) {
|
||||
if (!indices->label_index.CreateIndex(item, vertices->access()))
|
||||
throw RecoveryFailure("The label index must be created here!");
|
||||
spdlog::info("A label index is recreated from metadata.");
|
||||
}
|
||||
spdlog::info("Label indices are recreated.");
|
||||
|
||||
// Recover label+property indices.
|
||||
spdlog::info("Recreating {} label+property indices from metadata.",
|
||||
indices_constraints.indices.label_property.size());
|
||||
for (const auto &item : indices_constraints.indices.label_property) {
|
||||
if (!indices->label_property_index.CreateIndex(item.first, item.second, vertices->access()))
|
||||
throw RecoveryFailure("The label+property index must be created here!");
|
||||
spdlog::info("A label+property index is recreated from metadata.");
|
||||
}
|
||||
spdlog::info("Label+property indices are recreated.");
|
||||
spdlog::info("Indices are recreated.");
|
||||
|
||||
spdlog::info("Recreating constraints from metadata.");
|
||||
// Recover existence constraints.
|
||||
spdlog::info("Recreating {} existence constraints from metadata.", indices_constraints.constraints.existence.size());
|
||||
for (const auto &item : indices_constraints.constraints.existence) {
|
||||
auto ret = CreateExistenceConstraint(constraints, item.first, item.second, vertices->access());
|
||||
if (ret.HasError() || !ret.GetValue()) throw RecoveryFailure("The existence constraint must be created here!");
|
||||
spdlog::info("A existence constraint is recreated from metadata.");
|
||||
}
|
||||
spdlog::info("Existence constraints are recreated from metadata.");
|
||||
|
||||
// Recover unique constraints.
|
||||
spdlog::info("Recreating {} unique constraints from metadata.", indices_constraints.constraints.unique.size());
|
||||
for (const auto &item : indices_constraints.constraints.unique) {
|
||||
auto ret = constraints->unique_constraints.CreateConstraint(item.first, item.second, vertices->access());
|
||||
if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
|
||||
throw RecoveryFailure("The unique constraint must be created here!");
|
||||
spdlog::info("A unique constraint is recreated from metadata.");
|
||||
}
|
||||
spdlog::info("Unique constraints are recreated from metadata.");
|
||||
spdlog::info("Constraints are recreated from metadata.");
|
||||
}
|
||||
|
||||
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, std::string *uuid,
|
||||
std::string *epoch_id,
|
||||
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
|
||||
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
uint64_t *wal_seq_num) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory,
|
||||
wal_directory);
|
||||
if (!utils::DirExists(snapshot_directory) && !utils::DirExists(wal_directory)) {
|
||||
spdlog::warn(utils::MessageWithLink("Snapshot or WAL directory don't exist, there is nothing to recover.",
|
||||
"https://memgr.ph/durability"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto snapshot_files = GetSnapshotFiles(snapshot_directory);
|
||||
|
||||
RecoveryInfo recovery_info;
|
||||
RecoveredIndicesAndConstraints indices_constraints;
|
||||
std::optional<uint64_t> snapshot_timestamp;
|
||||
if (!snapshot_files.empty()) {
|
||||
spdlog::info("Try recovering from snapshot directory {}.", snapshot_directory);
|
||||
// Order the files by name
|
||||
std::sort(snapshot_files.begin(), snapshot_files.end());
|
||||
|
||||
// UUID used for durability is the UUID of the last snapshot file.
|
||||
*uuid = snapshot_files.back().uuid;
|
||||
std::optional<RecoveredSnapshot> recovered_snapshot;
|
||||
for (auto it = snapshot_files.rbegin(); it != snapshot_files.rend(); ++it) {
|
||||
const auto &[path, file_uuid, _] = *it;
|
||||
if (file_uuid != *uuid) {
|
||||
spdlog::warn("The snapshot file {} isn't related to the latest snapshot file!", path);
|
||||
continue;
|
||||
}
|
||||
spdlog::info("Starting snapshot recovery from {}.", path);
|
||||
try {
|
||||
recovered_snapshot = LoadSnapshot(path, vertices, edges, epoch_history, name_id_mapper, edge_count, items);
|
||||
spdlog::info("Snapshot recovery successful!");
|
||||
break;
|
||||
} catch (const RecoveryFailure &e) {
|
||||
spdlog::warn("Couldn't recover snapshot from {} because of: {}.", path, e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MG_ASSERT(recovered_snapshot,
|
||||
"The database is configured to recover on startup, but couldn't "
|
||||
"recover using any of the specified snapshots! Please inspect them "
|
||||
"and restart the database.");
|
||||
recovery_info = recovered_snapshot->recovery_info;
|
||||
indices_constraints = std::move(recovered_snapshot->indices_constraints);
|
||||
snapshot_timestamp = recovered_snapshot->snapshot_info.start_timestamp;
|
||||
*epoch_id = std::move(recovered_snapshot->snapshot_info.epoch_id);
|
||||
|
||||
if (!utils::DirExists(wal_directory)) {
|
||||
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices);
|
||||
return recovered_snapshot->recovery_info;
|
||||
}
|
||||
} else {
|
||||
spdlog::info("No snapshot file was found, collecting information from WAL directory {}.", wal_directory);
|
||||
std::error_code error_code;
|
||||
if (!utils::DirExists(wal_directory)) return std::nullopt;
|
||||
// We use this smaller struct that contains only a subset of information
|
||||
// necessary for the rest of the recovery function.
|
||||
// Also, the struct is sorted primarily on the path it contains.
|
||||
struct WalFileInfo {
|
||||
explicit WalFileInfo(std::filesystem::path path, std::string uuid, std::string epoch_id)
|
||||
: path(std::move(path)), uuid(std::move(uuid)), epoch_id(std::move(epoch_id)) {}
|
||||
std::filesystem::path path;
|
||||
std::string uuid;
|
||||
std::string epoch_id;
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr): bug in clang-tidy
|
||||
auto operator<=>(const WalFileInfo &) const = default;
|
||||
};
|
||||
std::vector<WalFileInfo> wal_files;
|
||||
for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) {
|
||||
if (!item.is_regular_file()) continue;
|
||||
try {
|
||||
auto info = ReadWalInfo(item.path());
|
||||
wal_files.emplace_back(item.path(), std::move(info.uuid), std::move(info.epoch_id));
|
||||
} catch (const RecoveryFailure &e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
|
||||
if (wal_files.empty()) {
|
||||
spdlog::warn(utils::MessageWithLink("No snapshot or WAL file found.", "https://memgr.ph/durability"));
|
||||
return std::nullopt;
|
||||
}
|
||||
std::sort(wal_files.begin(), wal_files.end());
|
||||
// UUID used for durability is the UUID of the last WAL file.
|
||||
// Same for the epoch id.
|
||||
*uuid = std::move(wal_files.back().uuid);
|
||||
*epoch_id = std::move(wal_files.back().epoch_id);
|
||||
}
|
||||
|
||||
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
|
||||
if (!maybe_wal_files) {
|
||||
spdlog::warn(
|
||||
utils::MessageWithLink("Couldn't get WAL file info from the WAL directory.", "https://memgr.ph/durability"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Array of all discovered WAL files, ordered by sequence number.
|
||||
auto &wal_files = *maybe_wal_files;
|
||||
|
||||
// By this point we should have recovered from a snapshot, or we should have
|
||||
// found some WAL files to recover from in the above `else`. This is just a
|
||||
// sanity check to circumvent the following case: The database didn't recover
|
||||
// from a snapshot, the above `else` triggered to find the recovery UUID from
|
||||
// a WAL file. The above `else` has an early exit in case there are no WAL
|
||||
// files. Because we reached this point there must have been some WAL files
|
||||
// and we must have some WAL files after this second WAL directory iteration.
|
||||
MG_ASSERT(snapshot_timestamp || !wal_files.empty(),
|
||||
"The database didn't recover from a snapshot and didn't find any WAL "
|
||||
"files that match the last WAL file!");
|
||||
|
||||
if (!wal_files.empty()) {
|
||||
spdlog::info("Checking WAL files.");
|
||||
{
|
||||
const auto &first_wal = wal_files[0];
|
||||
if (first_wal.seq_num != 0) {
|
||||
// We don't have all WAL files. We need to see whether we need them all.
|
||||
if (!snapshot_timestamp) {
|
||||
// We didn't recover from a snapshot and we must have all WAL files
|
||||
// starting from the first one (seq_num == 0) to be able to recover
|
||||
// data from them.
|
||||
LOG_FATAL(
|
||||
"There are missing prefix WAL files and data can't be "
|
||||
"recovered without them!");
|
||||
} else if (first_wal.from_timestamp >= *snapshot_timestamp) {
|
||||
// We recovered from a snapshot and we must have at least one WAL file
|
||||
// that has at least one delta that was created before the snapshot in order to
|
||||
// verify that nothing is missing from the beginning of the WAL chain.
|
||||
LOG_FATAL(
|
||||
"You must have at least one WAL file that contains at least one "
|
||||
"delta that was created before the snapshot file!");
|
||||
}
|
||||
}
|
||||
}
|
||||
std::optional<uint64_t> previous_seq_num;
|
||||
auto last_loaded_timestamp = snapshot_timestamp;
|
||||
spdlog::info("Trying to load WAL files.");
|
||||
for (auto &wal_file : wal_files) {
|
||||
if (previous_seq_num && (wal_file.seq_num - *previous_seq_num) > 1) {
|
||||
LOG_FATAL("You are missing a WAL file with the sequence number {}!", *previous_seq_num + 1);
|
||||
}
|
||||
previous_seq_num = wal_file.seq_num;
|
||||
|
||||
if (wal_file.epoch_id != *epoch_id) {
|
||||
// This way we skip WALs finalized only because of role change.
|
||||
// We can also set the last timestamp to 0 if last loaded timestamp
|
||||
// is nullopt as this can only happen if the WAL file with seq = 0
|
||||
// does not contain any deltas and we didn't find any snapshots.
|
||||
if (last_loaded_timestamp) {
|
||||
epoch_history->emplace_back(wal_file.epoch_id, *last_loaded_timestamp);
|
||||
}
|
||||
*epoch_id = std::move(wal_file.epoch_id);
|
||||
}
|
||||
try {
|
||||
auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,
|
||||
edge_count, items);
|
||||
recovery_info.next_vertex_id = std::max(recovery_info.next_vertex_id, info.next_vertex_id);
|
||||
recovery_info.next_edge_id = std::max(recovery_info.next_edge_id, info.next_edge_id);
|
||||
recovery_info.next_timestamp = std::max(recovery_info.next_timestamp, info.next_timestamp);
|
||||
|
||||
recovery_info.last_commit_timestamp = info.last_commit_timestamp;
|
||||
} catch (const RecoveryFailure &e) {
|
||||
LOG_FATAL("Couldn't recover WAL deltas from {} because of: {}", wal_file.path, e.what());
|
||||
}
|
||||
|
||||
if (recovery_info.next_timestamp != 0) {
|
||||
last_loaded_timestamp.emplace(recovery_info.next_timestamp - 1);
|
||||
}
|
||||
}
|
||||
// The sequence number needs to be recovered even though `LoadWal` didn't
|
||||
// load any deltas from that file.
|
||||
*wal_seq_num = *previous_seq_num + 1;
|
||||
|
||||
spdlog::info("All necessary WAL files are loaded successfully.");
|
||||
}
|
||||
|
||||
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices);
|
||||
return recovery_info;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,114 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/constraints.hpp"
|
||||
#include "storage/v3/durability/metadata.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Verifies that the owner of the storage directory is the same user that
|
||||
/// started the current process. If the verification fails, the process is
|
||||
/// killed (`CHECK` failure).
|
||||
void VerifyStorageDirectoryOwnerAndProcessUserOrDie(const std::filesystem::path &storage_directory);
|
||||
|
||||
// Used to capture the snapshot's data related to durability
|
||||
struct SnapshotDurabilityInfo {
|
||||
explicit SnapshotDurabilityInfo(std::filesystem::path path, std::string uuid, const uint64_t start_timestamp)
|
||||
: path(std::move(path)), uuid(std::move(uuid)), start_timestamp(start_timestamp) {}
|
||||
|
||||
std::filesystem::path path;
|
||||
std::string uuid;
|
||||
uint64_t start_timestamp;
|
||||
|
||||
auto operator<=>(const SnapshotDurabilityInfo &) const = default;
|
||||
};
|
||||
|
||||
/// Get list of snapshot files with their UUID.
|
||||
/// @param snapshot_directory Directory containing the Snapshot files.
|
||||
/// @param uuid UUID of the Snapshot files. If not empty, fetch only Snapshot
|
||||
/// file with the specified UUID. Otherwise, fetch only Snapshot files in the
|
||||
/// snapshot_directory.
|
||||
/// @return List of snapshot files defined with its path and UUID.
|
||||
std::vector<SnapshotDurabilityInfo> GetSnapshotFiles(const std::filesystem::path &snapshot_directory,
|
||||
std::string_view uuid = "");
|
||||
|
||||
/// Used to capture a WAL's data related to durability
|
||||
struct WalDurabilityInfo {
|
||||
explicit WalDurabilityInfo(const uint64_t seq_num, const uint64_t from_timestamp, const uint64_t to_timestamp,
|
||||
std::string uuid, std::string epoch_id, std::filesystem::path path)
|
||||
: seq_num(seq_num),
|
||||
from_timestamp(from_timestamp),
|
||||
to_timestamp(to_timestamp),
|
||||
uuid(std::move(uuid)),
|
||||
epoch_id(std::move(epoch_id)),
|
||||
path(std::move(path)) {}
|
||||
|
||||
uint64_t seq_num;
|
||||
uint64_t from_timestamp;
|
||||
uint64_t to_timestamp;
|
||||
std::string uuid;
|
||||
std::string epoch_id;
|
||||
std::filesystem::path path;
|
||||
|
||||
auto operator<=>(const WalDurabilityInfo &) const = default;
|
||||
};
|
||||
|
||||
/// Get list of WAL files ordered by the sequence number
|
||||
/// @param wal_directory Directory containing the WAL files.
|
||||
/// @param uuid UUID of the WAL files. If not empty, fetch only WAL files
|
||||
/// with the specified UUID. Otherwise, fetch all WAL files in the
|
||||
/// wal_directory.
|
||||
/// @param current_seq_num Sequence number of the WAL file which is currently
|
||||
/// being written. If specified, load only finalized WAL files, i.e. WAL files
|
||||
/// with seq_num < current_seq_num.
|
||||
/// @return List of WAL files. Each WAL file is defined with its sequence
|
||||
/// number, from timestamp, to timestamp and path.
|
||||
std::optional<std::vector<WalDurabilityInfo>> GetWalFiles(const std::filesystem::path &wal_directory,
|
||||
std::string_view uuid = "",
|
||||
std::optional<size_t> current_seq_num = {});
|
||||
|
||||
// Helper function used to recover all discovered indices and constraints. The
|
||||
// indices and constraints must be recovered after the data recovery is done
|
||||
// to ensure that the indices and constraints are consistent at the end of the
|
||||
// recovery process.
|
||||
/// @throw RecoveryFailure
|
||||
void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices,
|
||||
Constraints *constraints, utils::SkipList<Vertex> *vertices);
|
||||
|
||||
/// Recovers data either from a snapshot and/or WAL files.
|
||||
/// @throw RecoveryFailure
|
||||
/// @throw std::bad_alloc
|
||||
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, std::string *uuid,
|
||||
std::string *epoch_id,
|
||||
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
|
||||
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
uint64_t *wal_seq_num);
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,106 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Markers that are used to indicate crucial parts of the snapshot/WAL.
|
||||
/// IMPORTANT: Don't forget to update the list of all markers `kMarkersAll` when
|
||||
/// you add a new Marker.
|
||||
enum class Marker : uint8_t {
|
||||
TYPE_NULL = 0x10,
|
||||
TYPE_BOOL = 0x11,
|
||||
TYPE_INT = 0x12,
|
||||
TYPE_DOUBLE = 0x13,
|
||||
TYPE_STRING = 0x14,
|
||||
TYPE_LIST = 0x15,
|
||||
TYPE_MAP = 0x16,
|
||||
TYPE_PROPERTY_VALUE = 0x17,
|
||||
TYPE_TEMPORAL_DATA = 0x18,
|
||||
|
||||
SECTION_VERTEX = 0x20,
|
||||
SECTION_EDGE = 0x21,
|
||||
SECTION_MAPPER = 0x22,
|
||||
SECTION_METADATA = 0x23,
|
||||
SECTION_INDICES = 0x24,
|
||||
SECTION_CONSTRAINTS = 0x25,
|
||||
SECTION_DELTA = 0x26,
|
||||
SECTION_EPOCH_HISTORY = 0x27,
|
||||
SECTION_OFFSETS = 0x42,
|
||||
|
||||
DELTA_VERTEX_CREATE = 0x50,
|
||||
DELTA_VERTEX_DELETE = 0x51,
|
||||
DELTA_VERTEX_ADD_LABEL = 0x52,
|
||||
DELTA_VERTEX_REMOVE_LABEL = 0x53,
|
||||
DELTA_VERTEX_SET_PROPERTY = 0x54,
|
||||
DELTA_EDGE_CREATE = 0x55,
|
||||
DELTA_EDGE_DELETE = 0x56,
|
||||
DELTA_EDGE_SET_PROPERTY = 0x57,
|
||||
DELTA_TRANSACTION_END = 0x58,
|
||||
DELTA_LABEL_INDEX_CREATE = 0x59,
|
||||
DELTA_LABEL_INDEX_DROP = 0x5a,
|
||||
DELTA_LABEL_PROPERTY_INDEX_CREATE = 0x5b,
|
||||
DELTA_LABEL_PROPERTY_INDEX_DROP = 0x5c,
|
||||
DELTA_EXISTENCE_CONSTRAINT_CREATE = 0x5d,
|
||||
DELTA_EXISTENCE_CONSTRAINT_DROP = 0x5e,
|
||||
DELTA_UNIQUE_CONSTRAINT_CREATE = 0x5f,
|
||||
DELTA_UNIQUE_CONSTRAINT_DROP = 0x60,
|
||||
|
||||
VALUE_FALSE = 0x00,
|
||||
VALUE_TRUE = 0xff,
|
||||
};
|
||||
|
||||
/// List of all available markers.
|
||||
/// IMPORTANT: Don't forget to update this list when you add a new Marker.
|
||||
static const Marker kMarkersAll[] = {
|
||||
Marker::TYPE_NULL,
|
||||
Marker::TYPE_BOOL,
|
||||
Marker::TYPE_INT,
|
||||
Marker::TYPE_DOUBLE,
|
||||
Marker::TYPE_STRING,
|
||||
Marker::TYPE_LIST,
|
||||
Marker::TYPE_MAP,
|
||||
Marker::TYPE_TEMPORAL_DATA,
|
||||
Marker::TYPE_PROPERTY_VALUE,
|
||||
Marker::SECTION_VERTEX,
|
||||
Marker::SECTION_EDGE,
|
||||
Marker::SECTION_MAPPER,
|
||||
Marker::SECTION_METADATA,
|
||||
Marker::SECTION_INDICES,
|
||||
Marker::SECTION_CONSTRAINTS,
|
||||
Marker::SECTION_DELTA,
|
||||
Marker::SECTION_EPOCH_HISTORY,
|
||||
Marker::SECTION_OFFSETS,
|
||||
Marker::DELTA_VERTEX_CREATE,
|
||||
Marker::DELTA_VERTEX_DELETE,
|
||||
Marker::DELTA_VERTEX_ADD_LABEL,
|
||||
Marker::DELTA_VERTEX_REMOVE_LABEL,
|
||||
Marker::DELTA_VERTEX_SET_PROPERTY,
|
||||
Marker::DELTA_EDGE_CREATE,
|
||||
Marker::DELTA_EDGE_DELETE,
|
||||
Marker::DELTA_EDGE_SET_PROPERTY,
|
||||
Marker::DELTA_TRANSACTION_END,
|
||||
Marker::DELTA_LABEL_INDEX_CREATE,
|
||||
Marker::DELTA_LABEL_INDEX_DROP,
|
||||
Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE,
|
||||
Marker::DELTA_LABEL_PROPERTY_INDEX_DROP,
|
||||
Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE,
|
||||
Marker::DELTA_EXISTENCE_CONSTRAINT_DROP,
|
||||
Marker::DELTA_UNIQUE_CONSTRAINT_CREATE,
|
||||
Marker::DELTA_UNIQUE_CONSTRAINT_DROP,
|
||||
Marker::VALUE_FALSE,
|
||||
Marker::VALUE_TRUE,
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,75 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/durability/exceptions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Structure used to hold metadata about the recovered snapshot/WAL.
|
||||
struct RecoveryInfo {
|
||||
uint64_t next_vertex_id{0};
|
||||
uint64_t next_edge_id{0};
|
||||
uint64_t next_timestamp{0};
|
||||
|
||||
// last timestamp read from a WAL file
|
||||
std::optional<uint64_t> last_commit_timestamp;
|
||||
};
|
||||
|
||||
/// Structure used to track indices and constraints during recovery.
|
||||
struct RecoveredIndicesAndConstraints {
|
||||
struct {
|
||||
std::vector<LabelId> label;
|
||||
std::vector<std::pair<LabelId, PropertyId>> label_property;
|
||||
} indices;
|
||||
|
||||
struct {
|
||||
std::vector<std::pair<LabelId, PropertyId>> existence;
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||
} constraints;
|
||||
};
|
||||
|
||||
// Helper function used to insert indices/constraints into the recovered
|
||||
// indices/constraints object.
|
||||
// @throw RecoveryFailure
|
||||
template <typename TObj>
|
||||
void AddRecoveredIndexConstraint(std::vector<TObj> *list, TObj obj, const char *error_message) {
|
||||
auto it = std::find(list->begin(), list->end(), obj);
|
||||
if (it == list->end()) {
|
||||
list->push_back(obj);
|
||||
} else {
|
||||
throw RecoveryFailure(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function used to remove indices/constraints from the recovered
|
||||
// indices/constraints object.
|
||||
// @throw RecoveryFailure
|
||||
template <typename TObj>
|
||||
void RemoveRecoveredIndexConstraint(std::vector<TObj> *list, TObj obj, const char *error_message) {
|
||||
auto it = std::find(list->begin(), list->end(), obj);
|
||||
if (it != list->end()) {
|
||||
std::swap(*it, list->back());
|
||||
list->pop_back();
|
||||
} else {
|
||||
throw RecoveryFailure(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,50 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "utils/timestamp.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
static const std::string kSnapshotDirectory{"snapshots"};
|
||||
static const std::string kWalDirectory{"wal"};
|
||||
static const std::string kBackupDirectory{".backup"};
|
||||
static const std::string kLockFile{".lock"};
|
||||
|
||||
// This is the prefix used for Snapshot and WAL filenames. It is a timestamp
|
||||
// format that equals to: YYYYmmddHHMMSSffffff
|
||||
const std::string kTimestampFormat = "{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}{:06d}";
|
||||
|
||||
// Generates the name for a snapshot in a well-defined sortable format with the
|
||||
// start timestamp appended to the file name.
|
||||
inline std::string MakeSnapshotName(uint64_t start_timestamp) {
|
||||
std::string date_str = utils::Timestamp::Now().ToString(kTimestampFormat);
|
||||
return date_str + "_timestamp_" + std::to_string(start_timestamp);
|
||||
}
|
||||
|
||||
// Generates the name for a WAL file in a well-defined sortable format.
|
||||
inline std::string MakeWalName() {
|
||||
std::string date_str = utils::Timestamp::Now().ToString(kTimestampFormat);
|
||||
return date_str + "_current";
|
||||
}
|
||||
|
||||
// Generates the name for a WAL file in a well-defined sortable format with the
|
||||
// range of timestamps contained [from, to] appended to the name.
|
||||
inline std::string RemakeWalName(const std::string ¤t_name, uint64_t from_timestamp, uint64_t to_timestamp) {
|
||||
return current_name.substr(0, current_name.size() - 8) + "_from_" + std::to_string(from_timestamp) + "_to_" +
|
||||
std::to_string(to_timestamp);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,468 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/durability/serialization.hpp"
|
||||
|
||||
#include "storage/v3/temporal.hpp"
|
||||
#include "utils/endian.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
//////////////////////////
|
||||
// Encoder implementation.
|
||||
//////////////////////////
|
||||
|
||||
namespace {
|
||||
void WriteSize(Encoder *encoder, uint64_t size) {
|
||||
size = utils::HostToLittleEndian(size);
|
||||
encoder->Write(reinterpret_cast<const uint8_t *>(&size), sizeof(size));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Encoder::Initialize(const std::filesystem::path &path, const std::string_view &magic, uint64_t version) {
|
||||
file_.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING);
|
||||
Write(reinterpret_cast<const uint8_t *>(magic.data()), magic.size());
|
||||
auto version_encoded = utils::HostToLittleEndian(version);
|
||||
Write(reinterpret_cast<const uint8_t *>(&version_encoded), sizeof(version_encoded));
|
||||
}
|
||||
|
||||
void Encoder::OpenExisting(const std::filesystem::path &path) {
|
||||
file_.Open(path, utils::OutputFile::Mode::APPEND_TO_EXISTING);
|
||||
}
|
||||
|
||||
void Encoder::Close() {
|
||||
if (file_.IsOpen()) {
|
||||
file_.Close();
|
||||
}
|
||||
}
|
||||
|
||||
void Encoder::Write(const uint8_t *data, uint64_t size) { file_.Write(data, size); }
|
||||
|
||||
void Encoder::WriteMarker(Marker marker) {
|
||||
auto value = static_cast<uint8_t>(marker);
|
||||
Write(&value, sizeof(value));
|
||||
}
|
||||
|
||||
void Encoder::WriteBool(bool value) {
|
||||
WriteMarker(Marker::TYPE_BOOL);
|
||||
if (value) {
|
||||
WriteMarker(Marker::VALUE_TRUE);
|
||||
} else {
|
||||
WriteMarker(Marker::VALUE_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void Encoder::WriteUint(uint64_t value) {
|
||||
value = utils::HostToLittleEndian(value);
|
||||
WriteMarker(Marker::TYPE_INT);
|
||||
Write(reinterpret_cast<const uint8_t *>(&value), sizeof(value));
|
||||
}
|
||||
|
||||
void Encoder::WriteDouble(double value) {
|
||||
auto value_uint = utils::MemcpyCast<uint64_t>(value);
|
||||
value_uint = utils::HostToLittleEndian(value_uint);
|
||||
WriteMarker(Marker::TYPE_DOUBLE);
|
||||
Write(reinterpret_cast<const uint8_t *>(&value_uint), sizeof(value_uint));
|
||||
}
|
||||
|
||||
void Encoder::WriteString(const std::string_view &value) {
|
||||
WriteMarker(Marker::TYPE_STRING);
|
||||
WriteSize(this, value.size());
|
||||
Write(reinterpret_cast<const uint8_t *>(value.data()), value.size());
|
||||
}
|
||||
|
||||
void Encoder::WritePropertyValue(const PropertyValue &value) {
|
||||
WriteMarker(Marker::TYPE_PROPERTY_VALUE);
|
||||
switch (value.type()) {
|
||||
case PropertyValue::Type::Null: {
|
||||
WriteMarker(Marker::TYPE_NULL);
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::Bool: {
|
||||
WriteBool(value.ValueBool());
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::Int: {
|
||||
WriteUint(utils::MemcpyCast<uint64_t>(value.ValueInt()));
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::Double: {
|
||||
WriteDouble(value.ValueDouble());
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::String: {
|
||||
WriteString(value.ValueString());
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::List: {
|
||||
const auto &list = value.ValueList();
|
||||
WriteMarker(Marker::TYPE_LIST);
|
||||
WriteSize(this, list.size());
|
||||
for (const auto &item : list) {
|
||||
WritePropertyValue(item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::Map: {
|
||||
const auto &map = value.ValueMap();
|
||||
WriteMarker(Marker::TYPE_MAP);
|
||||
WriteSize(this, map.size());
|
||||
for (const auto &item : map) {
|
||||
WriteString(item.first);
|
||||
WritePropertyValue(item.second);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PropertyValue::Type::TemporalData: {
|
||||
const auto temporal_data = value.ValueTemporalData();
|
||||
WriteMarker(Marker::TYPE_TEMPORAL_DATA);
|
||||
WriteUint(static_cast<uint64_t>(temporal_data.type));
|
||||
WriteUint(utils::MemcpyCast<uint64_t>(temporal_data.microseconds));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Encoder::GetPosition() { return file_.GetPosition(); }
|
||||
|
||||
void Encoder::SetPosition(uint64_t position) {
|
||||
file_.SetPosition(utils::OutputFile::Position::SET, static_cast<ssize_t>(position));
|
||||
}
|
||||
|
||||
void Encoder::Sync() { file_.Sync(); }
|
||||
|
||||
void Encoder::Finalize() {
|
||||
file_.Sync();
|
||||
file_.Close();
|
||||
}
|
||||
|
||||
void Encoder::DisableFlushing() { file_.DisableFlushing(); }
|
||||
|
||||
void Encoder::EnableFlushing() { file_.EnableFlushing(); }
|
||||
|
||||
void Encoder::TryFlushing() { file_.TryFlushing(); }
|
||||
|
||||
std::pair<const uint8_t *, size_t> Encoder::CurrentFileBuffer() const { return file_.CurrentBuffer(); }
|
||||
|
||||
size_t Encoder::GetSize() { return file_.GetSize(); }
|
||||
|
||||
//////////////////////////
|
||||
// Decoder implementation.
|
||||
//////////////////////////
|
||||
|
||||
namespace {
|
||||
std::optional<Marker> CastToMarker(uint8_t value) {
|
||||
for (auto marker : kMarkersAll) {
|
||||
if (static_cast<uint8_t>(marker) == value) {
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> ReadSize(Decoder *decoder) {
|
||||
uint64_t size{0};
|
||||
if (!decoder->Read(reinterpret_cast<uint8_t *>(&size), sizeof(size))) return std::nullopt;
|
||||
size = utils::LittleEndianToHost(size);
|
||||
return size;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<uint64_t> Decoder::Initialize(const std::filesystem::path &path, const std::string &magic) {
|
||||
if (!file_.Open(path)) return std::nullopt;
|
||||
std::string file_magic(magic.size(), '\0');
|
||||
if (!Read(reinterpret_cast<uint8_t *>(file_magic.data()), file_magic.size())) return std::nullopt;
|
||||
if (file_magic != magic) return std::nullopt;
|
||||
uint64_t version_encoded{0};
|
||||
if (!Read(reinterpret_cast<uint8_t *>(&version_encoded), sizeof(version_encoded))) return std::nullopt;
|
||||
return utils::LittleEndianToHost(version_encoded);
|
||||
}
|
||||
|
||||
bool Decoder::Read(uint8_t *data, size_t size) { return file_.Read(data, size); }
|
||||
|
||||
bool Decoder::Peek(uint8_t *data, size_t size) { return file_.Peek(data, size); }
|
||||
|
||||
std::optional<Marker> Decoder::PeekMarker() {
|
||||
uint8_t value{0};
|
||||
if (!Peek(&value, sizeof(value))) return std::nullopt;
|
||||
auto marker = CastToMarker(value);
|
||||
if (!marker) return std::nullopt;
|
||||
return *marker;
|
||||
}
|
||||
|
||||
std::optional<Marker> Decoder::ReadMarker() {
|
||||
uint8_t value{0};
|
||||
if (!Read(&value, sizeof(value))) return std::nullopt;
|
||||
auto marker = CastToMarker(value);
|
||||
if (!marker) return std::nullopt;
|
||||
return *marker;
|
||||
}
|
||||
|
||||
std::optional<bool> Decoder::ReadBool() {
|
||||
auto marker = ReadMarker();
|
||||
if (!marker || *marker != Marker::TYPE_BOOL) return std::nullopt;
|
||||
auto value = ReadMarker();
|
||||
if (!value || (*value != Marker::VALUE_FALSE && *value != Marker::VALUE_TRUE)) return std::nullopt;
|
||||
return *value == Marker::VALUE_TRUE;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> Decoder::ReadUint() {
|
||||
auto marker = ReadMarker();
|
||||
if (!marker || *marker != Marker::TYPE_INT) return std::nullopt;
|
||||
uint64_t value{0};
|
||||
if (!Read(reinterpret_cast<uint8_t *>(&value), sizeof(value))) return std::nullopt;
|
||||
value = utils::LittleEndianToHost(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<double> Decoder::ReadDouble() {
|
||||
auto marker = ReadMarker();
|
||||
if (!marker || *marker != Marker::TYPE_DOUBLE) return std::nullopt;
|
||||
uint64_t value_int{0};
|
||||
if (!Read(reinterpret_cast<uint8_t *>(&value_int), sizeof(value_int))) return std::nullopt;
|
||||
value_int = utils::LittleEndianToHost(value_int);
|
||||
auto value = utils::MemcpyCast<double>(value_int);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<std::string> Decoder::ReadString() {
|
||||
auto marker = ReadMarker();
|
||||
if (!marker || *marker != Marker::TYPE_STRING) return std::nullopt;
|
||||
auto size = ReadSize(this);
|
||||
if (!size) return std::nullopt;
|
||||
std::string value(*size, '\0');
|
||||
if (!Read(reinterpret_cast<uint8_t *>(value.data()), *size)) return std::nullopt;
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::optional<TemporalData> ReadTemporalData(Decoder &decoder) {
|
||||
const auto inner_marker = decoder.ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_TEMPORAL_DATA) return std::nullopt;
|
||||
|
||||
const auto type = decoder.ReadUint();
|
||||
if (!type) return std::nullopt;
|
||||
|
||||
const auto microseconds = decoder.ReadUint();
|
||||
if (!microseconds) return std::nullopt;
|
||||
|
||||
return TemporalData{static_cast<TemporalType>(*type), utils::MemcpyCast<int64_t>(*microseconds)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<PropertyValue> Decoder::ReadPropertyValue() {
|
||||
auto pv_marker = ReadMarker();
|
||||
if (!pv_marker || *pv_marker != Marker::TYPE_PROPERTY_VALUE) return std::nullopt;
|
||||
|
||||
auto marker = PeekMarker();
|
||||
if (!marker) return std::nullopt;
|
||||
switch (*marker) {
|
||||
case Marker::TYPE_NULL: {
|
||||
auto inner_marker = ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_NULL) return std::nullopt;
|
||||
return PropertyValue();
|
||||
}
|
||||
case Marker::TYPE_BOOL: {
|
||||
auto value = ReadBool();
|
||||
if (!value) return std::nullopt;
|
||||
return PropertyValue(*value);
|
||||
}
|
||||
case Marker::TYPE_INT: {
|
||||
auto value = ReadUint();
|
||||
if (!value) return std::nullopt;
|
||||
return PropertyValue(utils::MemcpyCast<int64_t>(*value));
|
||||
}
|
||||
case Marker::TYPE_DOUBLE: {
|
||||
auto value = ReadDouble();
|
||||
if (!value) return std::nullopt;
|
||||
return PropertyValue(*value);
|
||||
}
|
||||
case Marker::TYPE_STRING: {
|
||||
auto value = ReadString();
|
||||
if (!value) return std::nullopt;
|
||||
return PropertyValue(std::move(*value));
|
||||
}
|
||||
case Marker::TYPE_LIST: {
|
||||
auto inner_marker = ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_LIST) return std::nullopt;
|
||||
auto size = ReadSize(this);
|
||||
if (!size) return std::nullopt;
|
||||
std::vector<PropertyValue> value;
|
||||
value.reserve(*size);
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto item = ReadPropertyValue();
|
||||
if (!item) return std::nullopt;
|
||||
value.emplace_back(std::move(*item));
|
||||
}
|
||||
return PropertyValue(std::move(value));
|
||||
}
|
||||
case Marker::TYPE_MAP: {
|
||||
auto inner_marker = ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_MAP) return std::nullopt;
|
||||
auto size = ReadSize(this);
|
||||
if (!size) return std::nullopt;
|
||||
std::map<std::string, PropertyValue> value;
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto key = ReadString();
|
||||
if (!key) return std::nullopt;
|
||||
auto item = ReadPropertyValue();
|
||||
if (!item) return std::nullopt;
|
||||
value.emplace(std::move(*key), std::move(*item));
|
||||
}
|
||||
return PropertyValue(std::move(value));
|
||||
}
|
||||
case Marker::TYPE_TEMPORAL_DATA: {
|
||||
const auto maybe_temporal_data = ReadTemporalData(*this);
|
||||
if (!maybe_temporal_data) return std::nullopt;
|
||||
return PropertyValue(*maybe_temporal_data);
|
||||
}
|
||||
|
||||
case Marker::TYPE_PROPERTY_VALUE:
|
||||
case Marker::SECTION_VERTEX:
|
||||
case Marker::SECTION_EDGE:
|
||||
case Marker::SECTION_MAPPER:
|
||||
case Marker::SECTION_METADATA:
|
||||
case Marker::SECTION_INDICES:
|
||||
case Marker::SECTION_CONSTRAINTS:
|
||||
case Marker::SECTION_DELTA:
|
||||
case Marker::SECTION_EPOCH_HISTORY:
|
||||
case Marker::SECTION_OFFSETS:
|
||||
case Marker::DELTA_VERTEX_CREATE:
|
||||
case Marker::DELTA_VERTEX_DELETE:
|
||||
case Marker::DELTA_VERTEX_ADD_LABEL:
|
||||
case Marker::DELTA_VERTEX_REMOVE_LABEL:
|
||||
case Marker::DELTA_VERTEX_SET_PROPERTY:
|
||||
case Marker::DELTA_EDGE_CREATE:
|
||||
case Marker::DELTA_EDGE_DELETE:
|
||||
case Marker::DELTA_EDGE_SET_PROPERTY:
|
||||
case Marker::DELTA_TRANSACTION_END:
|
||||
case Marker::DELTA_LABEL_INDEX_CREATE:
|
||||
case Marker::DELTA_LABEL_INDEX_DROP:
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
case Marker::VALUE_FALSE:
|
||||
case Marker::VALUE_TRUE:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool Decoder::SkipString() {
|
||||
auto marker = ReadMarker();
|
||||
if (!marker || *marker != Marker::TYPE_STRING) return false;
|
||||
auto maybe_size = ReadSize(this);
|
||||
if (!maybe_size) return false;
|
||||
|
||||
const uint64_t kBufferSize = 262144;
|
||||
uint8_t buffer[kBufferSize];
|
||||
uint64_t size = *maybe_size;
|
||||
while (size > 0) {
|
||||
uint64_t to_read = size < kBufferSize ? size : kBufferSize;
|
||||
if (!Read(reinterpret_cast<uint8_t *>(&buffer), to_read)) return false;
|
||||
size -= to_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Decoder::SkipPropertyValue() {
|
||||
auto pv_marker = ReadMarker();
|
||||
if (!pv_marker || *pv_marker != Marker::TYPE_PROPERTY_VALUE) return false;
|
||||
|
||||
auto marker = PeekMarker();
|
||||
if (!marker) return false;
|
||||
switch (*marker) {
|
||||
case Marker::TYPE_NULL: {
|
||||
auto inner_marker = ReadMarker();
|
||||
return inner_marker && *inner_marker == Marker::TYPE_NULL;
|
||||
}
|
||||
case Marker::TYPE_BOOL: {
|
||||
return !!ReadBool();
|
||||
}
|
||||
case Marker::TYPE_INT: {
|
||||
return !!ReadUint();
|
||||
}
|
||||
case Marker::TYPE_DOUBLE: {
|
||||
return !!ReadDouble();
|
||||
}
|
||||
case Marker::TYPE_STRING: {
|
||||
return SkipString();
|
||||
}
|
||||
case Marker::TYPE_LIST: {
|
||||
auto inner_marker = ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_LIST) return false;
|
||||
auto size = ReadSize(this);
|
||||
if (!size) return false;
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
if (!SkipPropertyValue()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Marker::TYPE_MAP: {
|
||||
auto inner_marker = ReadMarker();
|
||||
if (!inner_marker || *inner_marker != Marker::TYPE_MAP) return false;
|
||||
auto size = ReadSize(this);
|
||||
if (!size) return false;
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
if (!SkipString()) return false;
|
||||
if (!SkipPropertyValue()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Marker::TYPE_TEMPORAL_DATA: {
|
||||
return !!ReadTemporalData(*this);
|
||||
}
|
||||
|
||||
case Marker::TYPE_PROPERTY_VALUE:
|
||||
case Marker::SECTION_VERTEX:
|
||||
case Marker::SECTION_EDGE:
|
||||
case Marker::SECTION_MAPPER:
|
||||
case Marker::SECTION_METADATA:
|
||||
case Marker::SECTION_INDICES:
|
||||
case Marker::SECTION_CONSTRAINTS:
|
||||
case Marker::SECTION_DELTA:
|
||||
case Marker::SECTION_EPOCH_HISTORY:
|
||||
case Marker::SECTION_OFFSETS:
|
||||
case Marker::DELTA_VERTEX_CREATE:
|
||||
case Marker::DELTA_VERTEX_DELETE:
|
||||
case Marker::DELTA_VERTEX_ADD_LABEL:
|
||||
case Marker::DELTA_VERTEX_REMOVE_LABEL:
|
||||
case Marker::DELTA_VERTEX_SET_PROPERTY:
|
||||
case Marker::DELTA_EDGE_CREATE:
|
||||
case Marker::DELTA_EDGE_DELETE:
|
||||
case Marker::DELTA_EDGE_SET_PROPERTY:
|
||||
case Marker::DELTA_TRANSACTION_END:
|
||||
case Marker::DELTA_LABEL_INDEX_CREATE:
|
||||
case Marker::DELTA_LABEL_INDEX_DROP:
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
case Marker::VALUE_FALSE:
|
||||
case Marker::VALUE_TRUE:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint64_t> Decoder::GetSize() { return file_.GetSize(); }
|
||||
|
||||
std::optional<uint64_t> Decoder::GetPosition() { return file_.GetPosition(); }
|
||||
|
||||
bool Decoder::SetPosition(uint64_t position) {
|
||||
return !!file_.SetPosition(utils::InputFile::Position::SET, static_cast<ssize_t>(position));
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,143 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/durability/marker.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/file.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Encoder interface class. Used to implement streams to different targets
|
||||
/// (e.g. file and network).
|
||||
class BaseEncoder {
|
||||
protected:
|
||||
BaseEncoder() = default;
|
||||
~BaseEncoder() = default;
|
||||
|
||||
public:
|
||||
BaseEncoder(const BaseEncoder &) = delete;
|
||||
BaseEncoder(BaseEncoder &&) = delete;
|
||||
BaseEncoder &operator=(const BaseEncoder &) = delete;
|
||||
BaseEncoder &operator=(BaseEncoder &&) = delete;
|
||||
|
||||
virtual void WriteMarker(Marker marker) = 0;
|
||||
virtual void WriteBool(bool value) = 0;
|
||||
virtual void WriteUint(uint64_t value) = 0;
|
||||
virtual void WriteDouble(double value) = 0;
|
||||
virtual void WriteString(const std::string_view &value) = 0;
|
||||
virtual void WritePropertyValue(const PropertyValue &value) = 0;
|
||||
};
|
||||
|
||||
/// Encoder that is used to generate a snapshot/WAL.
|
||||
class Encoder final : public BaseEncoder {
|
||||
public:
|
||||
void Initialize(const std::filesystem::path &path, const std::string_view &magic, uint64_t version);
|
||||
|
||||
void OpenExisting(const std::filesystem::path &path);
|
||||
|
||||
void Close();
|
||||
// Main write function, the only one that is allowed to write to the `file_`
|
||||
// directly.
|
||||
void Write(const uint8_t *data, uint64_t size);
|
||||
|
||||
void WriteMarker(Marker marker) override;
|
||||
void WriteBool(bool value) override;
|
||||
void WriteUint(uint64_t value) override;
|
||||
void WriteDouble(double value) override;
|
||||
void WriteString(const std::string_view &value) override;
|
||||
void WritePropertyValue(const PropertyValue &value) override;
|
||||
|
||||
uint64_t GetPosition();
|
||||
void SetPosition(uint64_t position);
|
||||
|
||||
void Sync();
|
||||
|
||||
void Finalize();
|
||||
|
||||
// Disable flushing of the internal buffer.
|
||||
void DisableFlushing();
|
||||
// Enable flushing of the internal buffer.
|
||||
void EnableFlushing();
|
||||
// Try flushing the internal buffer.
|
||||
void TryFlushing();
|
||||
// Get the current internal buffer with its size.
|
||||
std::pair<const uint8_t *, size_t> CurrentFileBuffer() const;
|
||||
|
||||
// Get the total size of the current file.
|
||||
size_t GetSize();
|
||||
|
||||
private:
|
||||
utils::OutputFile file_;
|
||||
};
|
||||
|
||||
/// Decoder interface class. Used to implement streams from different sources
|
||||
/// (e.g. file and network).
|
||||
class BaseDecoder {
|
||||
protected:
|
||||
BaseDecoder() = default;
|
||||
~BaseDecoder() = default;
|
||||
|
||||
public:
|
||||
BaseDecoder(const BaseDecoder &) = delete;
|
||||
BaseDecoder(BaseDecoder &&) = delete;
|
||||
BaseDecoder &operator=(const BaseDecoder &) = delete;
|
||||
BaseDecoder &operator=(BaseDecoder &&) = delete;
|
||||
|
||||
virtual std::optional<Marker> ReadMarker() = 0;
|
||||
virtual std::optional<bool> ReadBool() = 0;
|
||||
virtual std::optional<uint64_t> ReadUint() = 0;
|
||||
virtual std::optional<double> ReadDouble() = 0;
|
||||
virtual std::optional<std::string> ReadString() = 0;
|
||||
virtual std::optional<PropertyValue> ReadPropertyValue() = 0;
|
||||
|
||||
virtual bool SkipString() = 0;
|
||||
virtual bool SkipPropertyValue() = 0;
|
||||
};
|
||||
|
||||
/// Decoder that is used to read a generated snapshot/WAL.
|
||||
class Decoder final : public BaseDecoder {
|
||||
public:
|
||||
std::optional<uint64_t> Initialize(const std::filesystem::path &path, const std::string &magic);
|
||||
|
||||
// Main read functions, the only one that are allowed to read from the `file_`
|
||||
// directly.
|
||||
bool Read(uint8_t *data, size_t size);
|
||||
bool Peek(uint8_t *data, size_t size);
|
||||
|
||||
std::optional<Marker> PeekMarker();
|
||||
|
||||
std::optional<Marker> ReadMarker() override;
|
||||
std::optional<bool> ReadBool() override;
|
||||
std::optional<uint64_t> ReadUint() override;
|
||||
std::optional<double> ReadDouble() override;
|
||||
std::optional<std::string> ReadString() override;
|
||||
std::optional<PropertyValue> ReadPropertyValue() override;
|
||||
|
||||
bool SkipString() override;
|
||||
bool SkipPropertyValue() override;
|
||||
|
||||
std::optional<uint64_t> GetSize();
|
||||
std::optional<uint64_t> GetPosition();
|
||||
bool SetPosition(uint64_t position);
|
||||
|
||||
private:
|
||||
utils::InputFile file_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,985 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/durability/snapshot.hpp"
|
||||
|
||||
#include "storage/v3/durability/exceptions.hpp"
|
||||
#include "storage/v3/durability/paths.hpp"
|
||||
#include "storage/v3/durability/serialization.hpp"
|
||||
#include "storage/v3/durability/version.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/edge_ref.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
// Snapshot format:
|
||||
//
|
||||
// 1) Magic string (non-encoded)
|
||||
//
|
||||
// 2) Snapshot version (non-encoded, little-endian)
|
||||
//
|
||||
// 3) Section offsets:
|
||||
// * offset to the first edge in the snapshot (`0` if properties on edges
|
||||
// are disabled)
|
||||
// * offset to the first vertex in the snapshot
|
||||
// * offset to the indices section
|
||||
// * offset to the constraints section
|
||||
// * offset to the mapper section
|
||||
// * offset to the metadata section
|
||||
//
|
||||
// 4) Encoded edges (if properties on edges are enabled); each edge is written
|
||||
// in the following format:
|
||||
// * gid
|
||||
// * properties
|
||||
//
|
||||
// 5) Encoded vertices; each vertex is written in the following format:
|
||||
// * gid
|
||||
// * labels
|
||||
// * properties
|
||||
// * in edges
|
||||
// * edge gid
|
||||
// * from vertex gid
|
||||
// * edge type
|
||||
// * out edges
|
||||
// * edge gid
|
||||
// * to vertex gid
|
||||
// * edge type
|
||||
//
|
||||
// 6) Indices
|
||||
// * label indices
|
||||
// * label
|
||||
// * label+property indices
|
||||
// * label
|
||||
// * property
|
||||
//
|
||||
// 7) Constraints
|
||||
// * existence constraints
|
||||
// * label
|
||||
// * property
|
||||
// * unique constraints (from version 13)
|
||||
// * label
|
||||
// * properties
|
||||
//
|
||||
// 8) Name to ID mapper data
|
||||
// * id to name mappings
|
||||
// * id
|
||||
// * name
|
||||
//
|
||||
// 9) Metadata
|
||||
// * storage UUID
|
||||
// * snapshot transaction start timestamp (required when recovering
|
||||
// from snapshot combined with WAL to determine what deltas need to be
|
||||
// applied)
|
||||
// * number of edges
|
||||
// * number of vertices
|
||||
//
|
||||
// IMPORTANT: When changing snapshot encoding/decoding bump the snapshot/WAL
|
||||
// version in `version.hpp`.
|
||||
|
||||
// Function used to read information about the snapshot file.
|
||||
SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) {
|
||||
// Check magic and version.
|
||||
Decoder snapshot;
|
||||
auto version = snapshot.Initialize(path, kSnapshotMagic);
|
||||
if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!");
|
||||
if (!IsVersionSupported(*version)) throw RecoveryFailure("Invalid snapshot version!");
|
||||
|
||||
// Prepare return value.
|
||||
SnapshotInfo info;
|
||||
|
||||
// Read offsets.
|
||||
{
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_OFFSETS) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
auto snapshot_size = snapshot.GetSize();
|
||||
if (!snapshot_size) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
auto read_offset = [&snapshot, snapshot_size] {
|
||||
auto maybe_offset = snapshot.ReadUint();
|
||||
if (!maybe_offset) throw RecoveryFailure("Invalid snapshot format!");
|
||||
auto offset = *maybe_offset;
|
||||
if (offset > *snapshot_size) throw RecoveryFailure("Invalid snapshot format!");
|
||||
return offset;
|
||||
};
|
||||
|
||||
info.offset_edges = read_offset();
|
||||
info.offset_vertices = read_offset();
|
||||
info.offset_indices = read_offset();
|
||||
info.offset_constraints = read_offset();
|
||||
info.offset_mapper = read_offset();
|
||||
info.offset_epoch_history = read_offset();
|
||||
info.offset_metadata = read_offset();
|
||||
}
|
||||
|
||||
// Read metadata.
|
||||
{
|
||||
if (!snapshot.SetPosition(info.offset_metadata)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_METADATA) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
auto maybe_uuid = snapshot.ReadString();
|
||||
if (!maybe_uuid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
info.uuid = std::move(*maybe_uuid);
|
||||
|
||||
auto maybe_epoch_id = snapshot.ReadString();
|
||||
if (!maybe_epoch_id) throw RecoveryFailure("Invalid snapshot data!");
|
||||
info.epoch_id = std::move(*maybe_epoch_id);
|
||||
|
||||
auto maybe_timestamp = snapshot.ReadUint();
|
||||
if (!maybe_timestamp) throw RecoveryFailure("Invalid snapshot data!");
|
||||
info.start_timestamp = *maybe_timestamp;
|
||||
|
||||
auto maybe_edges = snapshot.ReadUint();
|
||||
if (!maybe_edges) throw RecoveryFailure("Invalid snapshot data!");
|
||||
info.edges_count = *maybe_edges;
|
||||
|
||||
auto maybe_vertices = snapshot.ReadUint();
|
||||
if (!maybe_vertices) throw RecoveryFailure("Invalid snapshot data!");
|
||||
info.vertices_count = *maybe_vertices;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices,
|
||||
utils::SkipList<Edge> *edges,
|
||||
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
|
||||
NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, Config::Items items) {
|
||||
RecoveryInfo ret;
|
||||
RecoveredIndicesAndConstraints indices_constraints;
|
||||
|
||||
Decoder snapshot;
|
||||
auto version = snapshot.Initialize(path, kSnapshotMagic);
|
||||
if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!");
|
||||
if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version));
|
||||
|
||||
// Cleanup of loaded data in case of failure.
|
||||
bool success = false;
|
||||
utils::OnScopeExit cleanup([&] {
|
||||
if (!success) {
|
||||
edges->clear();
|
||||
vertices->clear();
|
||||
epoch_history->clear();
|
||||
}
|
||||
});
|
||||
|
||||
// Read snapshot info.
|
||||
const auto info = ReadSnapshotInfo(path);
|
||||
spdlog::info("Recovering {} vertices and {} edges.", info.vertices_count, info.edges_count);
|
||||
// Check for edges.
|
||||
bool snapshot_has_edges = info.offset_edges != 0;
|
||||
|
||||
// Recover mapper.
|
||||
std::unordered_map<uint64_t, uint64_t> snapshot_id_map;
|
||||
{
|
||||
spdlog::info("Recovering mapper metadata.");
|
||||
if (!snapshot.SetPosition(info.offset_mapper)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_MAPPER) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
auto size = snapshot.ReadUint();
|
||||
if (!size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto id = snapshot.ReadUint();
|
||||
if (!id) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto name = snapshot.ReadString();
|
||||
if (!name) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto my_id = name_id_mapper->NameToId(*name);
|
||||
snapshot_id_map.emplace(*id, my_id);
|
||||
SPDLOG_TRACE("Mapping \"{}\"from snapshot id {} to actual id {}.", *name, *id, my_id);
|
||||
}
|
||||
}
|
||||
auto get_label_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
|
||||
auto it = snapshot_id_map.find(snapshot_id);
|
||||
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
|
||||
return LabelId::FromUint(it->second);
|
||||
};
|
||||
auto get_property_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
|
||||
auto it = snapshot_id_map.find(snapshot_id);
|
||||
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
|
||||
return PropertyId::FromUint(it->second);
|
||||
};
|
||||
auto get_edge_type_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
|
||||
auto it = snapshot_id_map.find(snapshot_id);
|
||||
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
|
||||
return EdgeTypeId::FromUint(it->second);
|
||||
};
|
||||
|
||||
// Reset current edge count.
|
||||
edge_count->store(0, std::memory_order_release);
|
||||
|
||||
{
|
||||
// Recover edges.
|
||||
auto edge_acc = edges->access();
|
||||
uint64_t last_edge_gid = 0;
|
||||
if (snapshot_has_edges) {
|
||||
spdlog::info("Recovering {} edges.", info.edges_count);
|
||||
if (!snapshot.SetPosition(info.offset_edges)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
for (uint64_t i = 0; i < info.edges_count; ++i) {
|
||||
{
|
||||
const auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_EDGE) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
|
||||
if (items.properties_on_edges) {
|
||||
// Insert edge.
|
||||
auto gid = snapshot.ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
if (i > 0 && *gid <= last_edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
last_edge_gid = *gid;
|
||||
spdlog::debug("Recovering edge {} with properties.", *gid);
|
||||
auto [it, inserted] = edge_acc.insert(Edge{Gid::FromUint(*gid), nullptr});
|
||||
if (!inserted) throw RecoveryFailure("The edge must be inserted here!");
|
||||
|
||||
// Recover properties.
|
||||
{
|
||||
auto props_size = snapshot.ReadUint();
|
||||
if (!props_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto &props = it->properties;
|
||||
for (uint64_t j = 0; j < *props_size; ++j) {
|
||||
auto key = snapshot.ReadUint();
|
||||
if (!key) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto value = snapshot.ReadPropertyValue();
|
||||
if (!value) throw RecoveryFailure("Invalid snapshot data!");
|
||||
SPDLOG_TRACE("Recovered property \"{}\" with value \"{}\" for edge {}.",
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*key)), *value, *gid);
|
||||
props.SetProperty(get_property_from_id(*key), *value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Read edge GID.
|
||||
auto gid = snapshot.ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
if (i > 0 && *gid <= last_edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
last_edge_gid = *gid;
|
||||
|
||||
spdlog::debug("Ensuring edge {} doesn't have any properties.", *gid);
|
||||
// Read properties.
|
||||
{
|
||||
auto props_size = snapshot.ReadUint();
|
||||
if (!props_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
if (*props_size != 0)
|
||||
throw RecoveryFailure(
|
||||
"The snapshot has properties on edges, but the storage is "
|
||||
"configured without properties on edges!");
|
||||
}
|
||||
}
|
||||
}
|
||||
spdlog::info("Edges are recovered.");
|
||||
}
|
||||
|
||||
// Recover vertices (labels and properties).
|
||||
if (!snapshot.SetPosition(info.offset_vertices)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
auto vertex_acc = vertices->access();
|
||||
uint64_t last_vertex_gid = 0;
|
||||
spdlog::info("Recovering {} vertices.", info.vertices_count);
|
||||
for (uint64_t i = 0; i < info.vertices_count; ++i) {
|
||||
{
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_VERTEX) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
|
||||
// Insert vertex.
|
||||
auto gid = snapshot.ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
if (i > 0 && *gid <= last_vertex_gid) {
|
||||
throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
last_vertex_gid = *gid;
|
||||
spdlog::debug("Recovering vertex {}.", *gid);
|
||||
auto [it, inserted] = vertex_acc.insert(Vertex{Gid::FromUint(*gid), nullptr});
|
||||
if (!inserted) throw RecoveryFailure("The vertex must be inserted here!");
|
||||
|
||||
// Recover labels.
|
||||
spdlog::trace("Recovering labels for vertex {}.", *gid);
|
||||
{
|
||||
auto labels_size = snapshot.ReadUint();
|
||||
if (!labels_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto &labels = it->labels;
|
||||
labels.reserve(*labels_size);
|
||||
for (uint64_t j = 0; j < *labels_size; ++j) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
SPDLOG_TRACE("Recovered label \"{}\" for vertex {}.", name_id_mapper->IdToName(snapshot_id_map.at(*label)),
|
||||
*gid);
|
||||
labels.emplace_back(get_label_from_id(*label));
|
||||
}
|
||||
}
|
||||
|
||||
// Recover properties.
|
||||
spdlog::trace("Recovering properties for vertex {}.", *gid);
|
||||
{
|
||||
auto props_size = snapshot.ReadUint();
|
||||
if (!props_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto &props = it->properties;
|
||||
for (uint64_t j = 0; j < *props_size; ++j) {
|
||||
auto key = snapshot.ReadUint();
|
||||
if (!key) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto value = snapshot.ReadPropertyValue();
|
||||
if (!value) throw RecoveryFailure("Invalid snapshot data!");
|
||||
SPDLOG_TRACE("Recovered property \"{}\" with value \"{}\" for vertex {}.",
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*key)), *value, *gid);
|
||||
props.SetProperty(get_property_from_id(*key), *value);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip in edges.
|
||||
{
|
||||
auto in_size = snapshot.ReadUint();
|
||||
if (!in_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
for (uint64_t j = 0; j < *in_size; ++j) {
|
||||
auto edge_gid = snapshot.ReadUint();
|
||||
if (!edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto from_gid = snapshot.ReadUint();
|
||||
if (!from_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto edge_type = snapshot.ReadUint();
|
||||
if (!edge_type) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
}
|
||||
|
||||
// Skip out edges.
|
||||
auto out_size = snapshot.ReadUint();
|
||||
if (!out_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
for (uint64_t j = 0; j < *out_size; ++j) {
|
||||
auto edge_gid = snapshot.ReadUint();
|
||||
if (!edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto to_gid = snapshot.ReadUint();
|
||||
if (!to_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto edge_type = snapshot.ReadUint();
|
||||
if (!edge_type) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
}
|
||||
spdlog::info("Vertices are recovered.");
|
||||
|
||||
// Recover vertices (in/out edges).
|
||||
spdlog::info("Recovering connectivity.");
|
||||
if (!snapshot.SetPosition(info.offset_vertices)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
for (auto &vertex : vertex_acc) {
|
||||
{
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_VERTEX) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
|
||||
spdlog::trace("Recovering connectivity for vertex {}.", vertex.gid.AsUint());
|
||||
// Check vertex.
|
||||
auto gid = snapshot.ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
if (gid != vertex.gid.AsUint()) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
// Skip labels.
|
||||
{
|
||||
auto labels_size = snapshot.ReadUint();
|
||||
if (!labels_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
for (uint64_t j = 0; j < *labels_size; ++j) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
}
|
||||
|
||||
// Skip properties.
|
||||
{
|
||||
auto props_size = snapshot.ReadUint();
|
||||
if (!props_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
for (uint64_t j = 0; j < *props_size; ++j) {
|
||||
auto key = snapshot.ReadUint();
|
||||
if (!key) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto value = snapshot.SkipPropertyValue();
|
||||
if (!value) throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
}
|
||||
|
||||
// Recover in edges.
|
||||
{
|
||||
spdlog::trace("Recovering inbound edges for vertex {}.", vertex.gid.AsUint());
|
||||
auto in_size = snapshot.ReadUint();
|
||||
if (!in_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
vertex.in_edges.reserve(*in_size);
|
||||
for (uint64_t j = 0; j < *in_size; ++j) {
|
||||
auto edge_gid = snapshot.ReadUint();
|
||||
if (!edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
last_edge_gid = std::max(last_edge_gid, *edge_gid);
|
||||
|
||||
auto from_gid = snapshot.ReadUint();
|
||||
if (!from_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto edge_type = snapshot.ReadUint();
|
||||
if (!edge_type) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
auto from_vertex = vertex_acc.find(Gid::FromUint(*from_gid));
|
||||
if (from_vertex == vertex_acc.end()) throw RecoveryFailure("Invalid from vertex!");
|
||||
|
||||
EdgeRef edge_ref(Gid::FromUint(*edge_gid));
|
||||
if (items.properties_on_edges) {
|
||||
if (snapshot_has_edges) {
|
||||
auto edge = edge_acc.find(Gid::FromUint(*edge_gid));
|
||||
if (edge == edge_acc.end()) throw RecoveryFailure("Invalid edge!");
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
} else {
|
||||
auto [edge, inserted] = edge_acc.insert(Edge{Gid::FromUint(*edge_gid), nullptr});
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
}
|
||||
}
|
||||
SPDLOG_TRACE("Recovered inbound edge {} with label \"{}\" from vertex {}.", *edge_gid,
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), from_vertex->gid.AsUint());
|
||||
vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), &*from_vertex, edge_ref);
|
||||
}
|
||||
}
|
||||
|
||||
// Recover out edges.
|
||||
{
|
||||
spdlog::trace("Recovering outbound edges for vertex {}.", vertex.gid.AsUint());
|
||||
auto out_size = snapshot.ReadUint();
|
||||
if (!out_size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
vertex.out_edges.reserve(*out_size);
|
||||
for (uint64_t j = 0; j < *out_size; ++j) {
|
||||
auto edge_gid = snapshot.ReadUint();
|
||||
if (!edge_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
last_edge_gid = std::max(last_edge_gid, *edge_gid);
|
||||
|
||||
auto to_gid = snapshot.ReadUint();
|
||||
if (!to_gid) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto edge_type = snapshot.ReadUint();
|
||||
if (!edge_type) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
auto to_vertex = vertex_acc.find(Gid::FromUint(*to_gid));
|
||||
if (to_vertex == vertex_acc.end()) throw RecoveryFailure("Invalid to vertex!");
|
||||
|
||||
EdgeRef edge_ref(Gid::FromUint(*edge_gid));
|
||||
if (items.properties_on_edges) {
|
||||
if (snapshot_has_edges) {
|
||||
auto edge = edge_acc.find(Gid::FromUint(*edge_gid));
|
||||
if (edge == edge_acc.end()) throw RecoveryFailure("Invalid edge!");
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
} else {
|
||||
auto [edge, inserted] = edge_acc.insert(Edge{Gid::FromUint(*edge_gid), nullptr});
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
}
|
||||
}
|
||||
SPDLOG_TRACE("Recovered outbound edge {} with label \"{}\" to vertex {}.", *edge_gid,
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), to_vertex->gid.AsUint());
|
||||
vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), &*to_vertex, edge_ref);
|
||||
}
|
||||
// Increment edge count. We only increment the count here because the
|
||||
// information is duplicated in in_edges.
|
||||
edge_count->fetch_add(*out_size, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
spdlog::info("Connectivity is recovered.");
|
||||
|
||||
// Set initial values for edge/vertex ID generators.
|
||||
ret.next_edge_id = last_edge_gid + 1;
|
||||
ret.next_vertex_id = last_vertex_gid + 1;
|
||||
}
|
||||
|
||||
// Recover indices.
|
||||
{
|
||||
spdlog::info("Recovering metadata of indices.");
|
||||
if (!snapshot.SetPosition(info.offset_indices)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_INDICES) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
// Recover label indices.
|
||||
{
|
||||
auto size = snapshot.ReadUint();
|
||||
if (!size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
spdlog::info("Recovering metadata of {} label indices.", *size);
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
AddRecoveredIndexConstraint(&indices_constraints.indices.label, get_label_from_id(*label),
|
||||
"The label index already exists!");
|
||||
SPDLOG_TRACE("Recovered metadata of label index for :{}", name_id_mapper->IdToName(snapshot_id_map.at(*label)));
|
||||
}
|
||||
spdlog::info("Metadata of label indices are recovered.");
|
||||
}
|
||||
|
||||
// Recover label+property indices.
|
||||
{
|
||||
auto size = snapshot.ReadUint();
|
||||
if (!size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
spdlog::info("Recovering metadata of {} label+property indices.", *size);
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto property = snapshot.ReadUint();
|
||||
if (!property) throw RecoveryFailure("Invalid snapshot data!");
|
||||
AddRecoveredIndexConstraint(&indices_constraints.indices.label_property,
|
||||
{get_label_from_id(*label), get_property_from_id(*property)},
|
||||
"The label+property index already exists!");
|
||||
SPDLOG_TRACE("Recovered metadata of label+property index for :{}({})",
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*label)),
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*property)));
|
||||
}
|
||||
spdlog::info("Metadata of label+property indices are recovered.");
|
||||
}
|
||||
spdlog::info("Metadata of indices are recovered.");
|
||||
}
|
||||
|
||||
// Recover constraints.
|
||||
{
|
||||
spdlog::info("Recovering metadata of constraints.");
|
||||
if (!snapshot.SetPosition(info.offset_constraints)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_CONSTRAINTS) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
// Recover existence constraints.
|
||||
{
|
||||
auto size = snapshot.ReadUint();
|
||||
if (!size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
spdlog::info("Recovering metadata of {} existence constraints.", *size);
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto property = snapshot.ReadUint();
|
||||
if (!property) throw RecoveryFailure("Invalid snapshot data!");
|
||||
AddRecoveredIndexConstraint(&indices_constraints.constraints.existence,
|
||||
{get_label_from_id(*label), get_property_from_id(*property)},
|
||||
"The existence constraint already exists!");
|
||||
SPDLOG_TRACE("Recovered metadata of existence constraint for :{}({})",
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*label)),
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*property)));
|
||||
}
|
||||
spdlog::info("Metadata of existence constraints are recovered.");
|
||||
}
|
||||
|
||||
// Recover unique constraints.
|
||||
// Snapshot version should be checked since unique constraints were
|
||||
// implemented in later versions of snapshot.
|
||||
if (*version >= kUniqueConstraintVersion) {
|
||||
auto size = snapshot.ReadUint();
|
||||
if (!size) throw RecoveryFailure("Invalid snapshot data!");
|
||||
spdlog::info("Recovering metadata of {} unique constraints.", *size);
|
||||
for (uint64_t i = 0; i < *size; ++i) {
|
||||
auto label = snapshot.ReadUint();
|
||||
if (!label) throw RecoveryFailure("Invalid snapshot data!");
|
||||
auto properties_count = snapshot.ReadUint();
|
||||
if (!properties_count) throw RecoveryFailure("Invalid snapshot data!");
|
||||
std::set<PropertyId> properties;
|
||||
for (uint64_t j = 0; j < *properties_count; ++j) {
|
||||
auto property = snapshot.ReadUint();
|
||||
if (!property) throw RecoveryFailure("Invalid snapshot data!");
|
||||
properties.insert(get_property_from_id(*property));
|
||||
}
|
||||
AddRecoveredIndexConstraint(&indices_constraints.constraints.unique, {get_label_from_id(*label), properties},
|
||||
"The unique constraint already exists!");
|
||||
SPDLOG_TRACE("Recovered metadata of unique constraints for :{}",
|
||||
name_id_mapper->IdToName(snapshot_id_map.at(*label)));
|
||||
}
|
||||
spdlog::info("Metadata of unique constraints are recovered.");
|
||||
}
|
||||
spdlog::info("Metadata of constraints are recovered.");
|
||||
}
|
||||
|
||||
spdlog::info("Recovering metadata.");
|
||||
// Recover epoch history
|
||||
{
|
||||
if (!snapshot.SetPosition(info.offset_epoch_history)) throw RecoveryFailure("Couldn't read data from snapshot!");
|
||||
|
||||
const auto marker = snapshot.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_EPOCH_HISTORY) throw RecoveryFailure("Invalid snapshot data!");
|
||||
|
||||
const auto history_size = snapshot.ReadUint();
|
||||
if (!history_size) {
|
||||
throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
|
||||
for (int i = 0; i < *history_size; ++i) {
|
||||
auto maybe_epoch_id = snapshot.ReadString();
|
||||
if (!maybe_epoch_id) {
|
||||
throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
const auto maybe_last_commit_timestamp = snapshot.ReadUint();
|
||||
if (!maybe_last_commit_timestamp) {
|
||||
throw RecoveryFailure("Invalid snapshot data!");
|
||||
}
|
||||
epoch_history->emplace_back(std::move(*maybe_epoch_id), *maybe_last_commit_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("Metadata recovered.");
|
||||
// Recover timestamp.
|
||||
ret.next_timestamp = info.start_timestamp + 1;
|
||||
|
||||
// Set success flag (to disable cleanup).
|
||||
success = true;
|
||||
|
||||
return {info, ret, std::move(indices_constraints)};
|
||||
}
|
||||
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
const SchemaValidator &schema_validator, const std::string &uuid, const std::string_view epoch_id,
|
||||
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer) {
|
||||
// Ensure that the storage directory exists.
|
||||
utils::EnsureDirOrDie(snapshot_directory);
|
||||
|
||||
// Create snapshot file.
|
||||
auto path = snapshot_directory / MakeSnapshotName(transaction->start_timestamp);
|
||||
spdlog::info("Starting snapshot creation to {}", path);
|
||||
Encoder snapshot;
|
||||
snapshot.Initialize(path, kSnapshotMagic, kVersion);
|
||||
|
||||
// Write placeholder offsets.
|
||||
uint64_t offset_offsets = 0;
|
||||
uint64_t offset_edges = 0;
|
||||
uint64_t offset_vertices = 0;
|
||||
uint64_t offset_indices = 0;
|
||||
uint64_t offset_constraints = 0;
|
||||
uint64_t offset_mapper = 0;
|
||||
uint64_t offset_metadata = 0;
|
||||
uint64_t offset_epoch_history = 0;
|
||||
{
|
||||
snapshot.WriteMarker(Marker::SECTION_OFFSETS);
|
||||
offset_offsets = snapshot.GetPosition();
|
||||
snapshot.WriteUint(offset_edges);
|
||||
snapshot.WriteUint(offset_vertices);
|
||||
snapshot.WriteUint(offset_indices);
|
||||
snapshot.WriteUint(offset_constraints);
|
||||
snapshot.WriteUint(offset_mapper);
|
||||
snapshot.WriteUint(offset_epoch_history);
|
||||
snapshot.WriteUint(offset_metadata);
|
||||
}
|
||||
|
||||
// Object counters.
|
||||
uint64_t edges_count = 0;
|
||||
uint64_t vertices_count = 0;
|
||||
|
||||
// Mapper data.
|
||||
std::unordered_set<uint64_t> used_ids;
|
||||
auto write_mapping = [&snapshot, &used_ids](auto mapping) {
|
||||
used_ids.insert(mapping.AsUint());
|
||||
snapshot.WriteUint(mapping.AsUint());
|
||||
};
|
||||
|
||||
// Store all edges.
|
||||
if (items.properties_on_edges) {
|
||||
offset_edges = snapshot.GetPosition();
|
||||
auto acc = edges->access();
|
||||
for (auto &edge : acc) {
|
||||
// The edge visibility check must be done here manually because we don't
|
||||
// allow direct access to the edges through the public API.
|
||||
bool is_visible = true;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge.lock);
|
||||
is_visible = !edge.deleted;
|
||||
delta = edge.delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction, delta, View::OLD, [&is_visible](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
is_visible = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
is_visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!is_visible) continue;
|
||||
EdgeRef edge_ref(&edge);
|
||||
// Here we create an edge accessor that we will use to get the
|
||||
// properties of the edge. The accessor is created with an invalid
|
||||
// type and invalid from/to pointers because we don't know them here,
|
||||
// but that isn't an issue because we won't use that part of the API
|
||||
// here.
|
||||
// TODO(jbajic) Fix snapshot with new schema rules
|
||||
auto ea = EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints,
|
||||
items, schema_validator};
|
||||
|
||||
// Get edge data.
|
||||
auto maybe_props = ea.Properties(View::OLD);
|
||||
MG_ASSERT(maybe_props.HasValue(), "Invalid database state!");
|
||||
|
||||
// Store the edge.
|
||||
{
|
||||
snapshot.WriteMarker(Marker::SECTION_EDGE);
|
||||
snapshot.WriteUint(edge.gid.AsUint());
|
||||
const auto &props = maybe_props.GetValue();
|
||||
snapshot.WriteUint(props.size());
|
||||
for (const auto &item : props) {
|
||||
write_mapping(item.first);
|
||||
snapshot.WritePropertyValue(item.second);
|
||||
}
|
||||
}
|
||||
|
||||
++edges_count;
|
||||
}
|
||||
}
|
||||
|
||||
// Store all vertices.
|
||||
{
|
||||
offset_vertices = snapshot.GetPosition();
|
||||
auto acc = vertices->access();
|
||||
for (auto &vertex : acc) {
|
||||
// The visibility check is implemented for vertices so we use it here.
|
||||
auto va = VertexAccessor::Create(&vertex, transaction, indices, constraints, items, schema_validator, View::OLD);
|
||||
if (!va) continue;
|
||||
|
||||
// Get vertex data.
|
||||
// TODO (mferencevic): All of these functions could be written into a
|
||||
// single function so that we traverse the undo deltas only once.
|
||||
auto maybe_labels = va->Labels(View::OLD);
|
||||
MG_ASSERT(maybe_labels.HasValue(), "Invalid database state!");
|
||||
auto maybe_props = va->Properties(View::OLD);
|
||||
MG_ASSERT(maybe_props.HasValue(), "Invalid database state!");
|
||||
auto maybe_in_edges = va->InEdges(View::OLD);
|
||||
MG_ASSERT(maybe_in_edges.HasValue(), "Invalid database state!");
|
||||
auto maybe_out_edges = va->OutEdges(View::OLD);
|
||||
MG_ASSERT(maybe_out_edges.HasValue(), "Invalid database state!");
|
||||
|
||||
// Store the vertex.
|
||||
{
|
||||
snapshot.WriteMarker(Marker::SECTION_VERTEX);
|
||||
snapshot.WriteUint(vertex.gid.AsUint());
|
||||
const auto &labels = maybe_labels.GetValue();
|
||||
snapshot.WriteUint(labels.size());
|
||||
for (const auto &item : labels) {
|
||||
write_mapping(item);
|
||||
}
|
||||
const auto &props = maybe_props.GetValue();
|
||||
snapshot.WriteUint(props.size());
|
||||
for (const auto &item : props) {
|
||||
write_mapping(item.first);
|
||||
snapshot.WritePropertyValue(item.second);
|
||||
}
|
||||
const auto &in_edges = maybe_in_edges.GetValue();
|
||||
snapshot.WriteUint(in_edges.size());
|
||||
for (const auto &item : in_edges) {
|
||||
snapshot.WriteUint(item.Gid().AsUint());
|
||||
snapshot.WriteUint(item.FromVertex().Gid().AsUint());
|
||||
write_mapping(item.EdgeType());
|
||||
}
|
||||
const auto &out_edges = maybe_out_edges.GetValue();
|
||||
snapshot.WriteUint(out_edges.size());
|
||||
for (const auto &item : out_edges) {
|
||||
snapshot.WriteUint(item.Gid().AsUint());
|
||||
snapshot.WriteUint(item.ToVertex().Gid().AsUint());
|
||||
write_mapping(item.EdgeType());
|
||||
}
|
||||
}
|
||||
|
||||
++vertices_count;
|
||||
}
|
||||
}
|
||||
|
||||
// Write indices.
|
||||
{
|
||||
offset_indices = snapshot.GetPosition();
|
||||
snapshot.WriteMarker(Marker::SECTION_INDICES);
|
||||
|
||||
// Write label indices.
|
||||
{
|
||||
auto label = indices->label_index.ListIndices();
|
||||
snapshot.WriteUint(label.size());
|
||||
for (const auto &item : label) {
|
||||
write_mapping(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Write label+property indices.
|
||||
{
|
||||
auto label_property = indices->label_property_index.ListIndices();
|
||||
snapshot.WriteUint(label_property.size());
|
||||
for (const auto &item : label_property) {
|
||||
write_mapping(item.first);
|
||||
write_mapping(item.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write constraints.
|
||||
{
|
||||
offset_constraints = snapshot.GetPosition();
|
||||
snapshot.WriteMarker(Marker::SECTION_CONSTRAINTS);
|
||||
|
||||
// Write existence constraints.
|
||||
{
|
||||
auto existence = ListExistenceConstraints(*constraints);
|
||||
snapshot.WriteUint(existence.size());
|
||||
for (const auto &item : existence) {
|
||||
write_mapping(item.first);
|
||||
write_mapping(item.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Write unique constraints.
|
||||
{
|
||||
auto unique = constraints->unique_constraints.ListConstraints();
|
||||
snapshot.WriteUint(unique.size());
|
||||
for (const auto &item : unique) {
|
||||
write_mapping(item.first);
|
||||
snapshot.WriteUint(item.second.size());
|
||||
for (const auto &property : item.second) {
|
||||
write_mapping(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write mapper data.
|
||||
{
|
||||
offset_mapper = snapshot.GetPosition();
|
||||
snapshot.WriteMarker(Marker::SECTION_MAPPER);
|
||||
snapshot.WriteUint(used_ids.size());
|
||||
for (auto item : used_ids) {
|
||||
snapshot.WriteUint(item);
|
||||
snapshot.WriteString(name_id_mapper->IdToName(item));
|
||||
}
|
||||
}
|
||||
|
||||
// Write epoch history
|
||||
{
|
||||
offset_epoch_history = snapshot.GetPosition();
|
||||
snapshot.WriteMarker(Marker::SECTION_EPOCH_HISTORY);
|
||||
snapshot.WriteUint(epoch_history.size());
|
||||
for (const auto &[epoch_id, last_commit_timestamp] : epoch_history) {
|
||||
snapshot.WriteString(epoch_id);
|
||||
snapshot.WriteUint(last_commit_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// Write metadata.
|
||||
{
|
||||
offset_metadata = snapshot.GetPosition();
|
||||
snapshot.WriteMarker(Marker::SECTION_METADATA);
|
||||
snapshot.WriteString(uuid);
|
||||
snapshot.WriteString(epoch_id);
|
||||
snapshot.WriteUint(transaction->start_timestamp);
|
||||
snapshot.WriteUint(edges_count);
|
||||
snapshot.WriteUint(vertices_count);
|
||||
}
|
||||
|
||||
// Write true offsets.
|
||||
{
|
||||
snapshot.SetPosition(offset_offsets);
|
||||
snapshot.WriteUint(offset_edges);
|
||||
snapshot.WriteUint(offset_vertices);
|
||||
snapshot.WriteUint(offset_indices);
|
||||
snapshot.WriteUint(offset_constraints);
|
||||
snapshot.WriteUint(offset_mapper);
|
||||
snapshot.WriteUint(offset_epoch_history);
|
||||
snapshot.WriteUint(offset_metadata);
|
||||
}
|
||||
|
||||
// Finalize snapshot file.
|
||||
snapshot.Finalize();
|
||||
spdlog::info("Snapshot creation successful!");
|
||||
|
||||
// Ensure exactly `snapshot_retention_count` snapshots exist.
|
||||
std::vector<std::pair<uint64_t, std::filesystem::path>> old_snapshot_files;
|
||||
{
|
||||
std::error_code error_code;
|
||||
for (const auto &item : std::filesystem::directory_iterator(snapshot_directory, error_code)) {
|
||||
if (!item.is_regular_file()) continue;
|
||||
if (item.path() == path) continue;
|
||||
try {
|
||||
auto info = ReadSnapshotInfo(item.path());
|
||||
if (info.uuid != uuid) continue;
|
||||
old_snapshot_files.emplace_back(info.start_timestamp, item.path());
|
||||
} catch (const RecoveryFailure &e) {
|
||||
spdlog::warn("Found a corrupt snapshot file {} becuase of: {}", item.path(), e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (error_code) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Couldn't ensure that exactly {} snapshots exist because an error occurred: {}.",
|
||||
snapshot_retention_count, error_code.message(), "https://memgr.ph/snapshots"));
|
||||
}
|
||||
std::sort(old_snapshot_files.begin(), old_snapshot_files.end());
|
||||
if (old_snapshot_files.size() > snapshot_retention_count - 1) {
|
||||
auto num_to_erase = old_snapshot_files.size() - (snapshot_retention_count - 1);
|
||||
for (size_t i = 0; i < num_to_erase; ++i) {
|
||||
const auto &[start_timestamp, snapshot_path] = old_snapshot_files[i];
|
||||
file_retainer->DeleteFile(snapshot_path);
|
||||
}
|
||||
old_snapshot_files.erase(
|
||||
old_snapshot_files.begin(),
|
||||
old_snapshot_files.begin() +
|
||||
static_cast<typename decltype(old_snapshot_files)::iterator::difference_type>(num_to_erase));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that only the absolutely necessary WAL files exist.
|
||||
if (old_snapshot_files.size() == snapshot_retention_count - 1 && utils::DirExists(wal_directory)) {
|
||||
std::vector<std::tuple<uint64_t, uint64_t, uint64_t, std::filesystem::path>> wal_files;
|
||||
std::error_code error_code;
|
||||
for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) {
|
||||
if (!item.is_regular_file()) continue;
|
||||
try {
|
||||
auto info = ReadWalInfo(item.path());
|
||||
if (info.uuid != uuid) continue;
|
||||
wal_files.emplace_back(info.seq_num, info.from_timestamp, info.to_timestamp, item.path());
|
||||
} catch (const RecoveryFailure &e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (error_code) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Couldn't ensure that only the absolutely necessary WAL files exist "
|
||||
"because an error occurred: {}.",
|
||||
error_code.message(), "https://memgr.ph/snapshots"));
|
||||
}
|
||||
std::sort(wal_files.begin(), wal_files.end());
|
||||
uint64_t snapshot_start_timestamp = transaction->start_timestamp;
|
||||
if (!old_snapshot_files.empty()) {
|
||||
snapshot_start_timestamp = old_snapshot_files.front().first;
|
||||
}
|
||||
std::optional<uint64_t> pos = 0;
|
||||
for (uint64_t i = 0; i < wal_files.size(); ++i) {
|
||||
const auto &[seq_num, from_timestamp, to_timestamp, wal_path] = wal_files[i];
|
||||
if (from_timestamp <= snapshot_start_timestamp) {
|
||||
pos = i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos && *pos > 0) {
|
||||
// We need to leave at least one WAL file that contains deltas that were
|
||||
// created before the oldest snapshot. Because we always leave at least
|
||||
// one WAL file that contains deltas before the snapshot, this correctly
|
||||
// handles the edge case when that one file is the current WAL file that
|
||||
// is being appended to.
|
||||
for (uint64_t i = 0; i < *pos; ++i) {
|
||||
const auto &[seq_num, from_timestamp, to_timestamp, wal_path] = wal_files[i];
|
||||
file_retainer->DeleteFile(wal_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,77 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/constraints.hpp"
|
||||
#include "storage/v3/durability/metadata.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Structure used to hold information about a snapshot.
|
||||
struct SnapshotInfo {
|
||||
uint64_t offset_edges;
|
||||
uint64_t offset_vertices;
|
||||
uint64_t offset_indices;
|
||||
uint64_t offset_constraints;
|
||||
uint64_t offset_mapper;
|
||||
uint64_t offset_epoch_history;
|
||||
uint64_t offset_metadata;
|
||||
|
||||
std::string uuid;
|
||||
std::string epoch_id;
|
||||
uint64_t start_timestamp;
|
||||
uint64_t edges_count;
|
||||
uint64_t vertices_count;
|
||||
};
|
||||
|
||||
/// Structure used to hold information about the snapshot that has been
|
||||
/// recovered.
|
||||
struct RecoveredSnapshot {
|
||||
SnapshotInfo snapshot_info;
|
||||
RecoveryInfo recovery_info;
|
||||
RecoveredIndicesAndConstraints indices_constraints;
|
||||
};
|
||||
|
||||
/// Function used to read information about the snapshot file.
|
||||
/// @throw RecoveryFailure
|
||||
SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path);
|
||||
|
||||
/// Function used to load the snapshot data into the storage.
|
||||
/// @throw RecoveryFailure
|
||||
RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices,
|
||||
utils::SkipList<Edge> *edges,
|
||||
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
|
||||
NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, Config::Items items);
|
||||
|
||||
/// Function used to create a snapshot using the given transaction.
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
const SchemaValidator &schema_validator, const std::string &uuid, std::string_view epoch_id,
|
||||
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer);
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,37 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
// The current version of snapshot and WAL encoding / decoding.
|
||||
// IMPORTANT: Please bump this version for every snapshot and/or WAL format
|
||||
// change!!!
|
||||
const uint64_t kVersion{14};
|
||||
|
||||
const uint64_t kOldestSupportedVersion{14};
|
||||
const uint64_t kUniqueConstraintVersion{13};
|
||||
|
||||
// Magic values written to the start of a snapshot/WAL file to identify it.
|
||||
const std::string kSnapshotMagic{"MGsn"};
|
||||
const std::string kWalMagic{"MGwl"};
|
||||
|
||||
static_assert(std::is_same_v<uint8_t, unsigned char>);
|
||||
|
||||
// Checks whether the loaded snapshot/WAL version is supported.
|
||||
inline bool IsVersionSupported(uint64_t version) { return version >= kOldestSupportedVersion && version <= kVersion; }
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,988 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
|
||||
#include "storage/v3/delta.hpp"
|
||||
#include "storage/v3/durability/exceptions.hpp"
|
||||
#include "storage/v3/durability/paths.hpp"
|
||||
#include "storage/v3/durability/version.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
// WAL format:
|
||||
//
|
||||
// 1) Magic string (non-encoded)
|
||||
//
|
||||
// 2) WAL version (non-encoded, little-endian)
|
||||
//
|
||||
// 3) Section offsets:
|
||||
// * offset to the metadata section
|
||||
// * offset to the first delta in the WAL
|
||||
//
|
||||
// 4) Metadata
|
||||
// * storage UUID
|
||||
// * sequence number (number indicating the sequence position of this WAL
|
||||
// file)
|
||||
//
|
||||
// 5) Encoded deltas; each delta is written in the following format:
|
||||
// * commit timestamp
|
||||
// * action (only one of the actions below are encoded)
|
||||
// * vertex create, vertex delete
|
||||
// * gid
|
||||
// * vertex add label, vertex remove label
|
||||
// * gid
|
||||
// * label name
|
||||
// * vertex set property
|
||||
// * gid
|
||||
// * property name
|
||||
// * property value
|
||||
// * edge create, edge delete
|
||||
// * gid
|
||||
// * edge type name
|
||||
// * from vertex gid
|
||||
// * to vertex gid
|
||||
// * edge set property
|
||||
// * gid
|
||||
// * property name
|
||||
// * property value
|
||||
// * transaction end (marks that the whole transaction is
|
||||
// stored in the WAL file)
|
||||
// * label index create, label index drop
|
||||
// * label name
|
||||
// * label property index create, label property index drop,
|
||||
// existence constraint create, existence constraint drop
|
||||
// * label name
|
||||
// * property name
|
||||
// * unique constraint create, unique constraint drop
|
||||
// * label name
|
||||
// * property names
|
||||
//
|
||||
// IMPORTANT: When changing WAL encoding/decoding bump the snapshot/WAL version
|
||||
// in `version.hpp`.
|
||||
|
||||
namespace {
|
||||
|
||||
Marker OperationToMarker(StorageGlobalOperation operation) {
|
||||
switch (operation) {
|
||||
case StorageGlobalOperation::LABEL_INDEX_CREATE:
|
||||
return Marker::DELTA_LABEL_INDEX_CREATE;
|
||||
case StorageGlobalOperation::LABEL_INDEX_DROP:
|
||||
return Marker::DELTA_LABEL_INDEX_DROP;
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE:
|
||||
return Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE;
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP:
|
||||
return Marker::DELTA_LABEL_PROPERTY_INDEX_DROP;
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE:
|
||||
return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE;
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP:
|
||||
return Marker::DELTA_EXISTENCE_CONSTRAINT_DROP;
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE:
|
||||
return Marker::DELTA_UNIQUE_CONSTRAINT_CREATE;
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP:
|
||||
return Marker::DELTA_UNIQUE_CONSTRAINT_DROP;
|
||||
}
|
||||
}
|
||||
|
||||
Marker VertexActionToMarker(Delta::Action action) {
|
||||
// When converting a Delta to a WAL delta the logic is inverted. That is
|
||||
// because the Delta's represent undo actions and we want to store redo
|
||||
// actions.
|
||||
switch (action) {
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
return Marker::DELTA_VERTEX_CREATE;
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
return Marker::DELTA_VERTEX_DELETE;
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
return Marker::DELTA_VERTEX_SET_PROPERTY;
|
||||
case Delta::Action::ADD_LABEL:
|
||||
return Marker::DELTA_VERTEX_REMOVE_LABEL;
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
return Marker::DELTA_VERTEX_ADD_LABEL;
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
return Marker::DELTA_EDGE_DELETE;
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
return Marker::DELTA_EDGE_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
// This function convertes a Marker to a WalDeltaData::Type. It checks for the
|
||||
// validity of the marker and throws if an invalid marker is specified.
|
||||
// @throw RecoveryFailure
|
||||
WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) {
|
||||
switch (marker) {
|
||||
case Marker::DELTA_VERTEX_CREATE:
|
||||
return WalDeltaData::Type::VERTEX_CREATE;
|
||||
case Marker::DELTA_VERTEX_DELETE:
|
||||
return WalDeltaData::Type::VERTEX_DELETE;
|
||||
case Marker::DELTA_VERTEX_ADD_LABEL:
|
||||
return WalDeltaData::Type::VERTEX_ADD_LABEL;
|
||||
case Marker::DELTA_VERTEX_REMOVE_LABEL:
|
||||
return WalDeltaData::Type::VERTEX_REMOVE_LABEL;
|
||||
case Marker::DELTA_EDGE_CREATE:
|
||||
return WalDeltaData::Type::EDGE_CREATE;
|
||||
case Marker::DELTA_EDGE_DELETE:
|
||||
return WalDeltaData::Type::EDGE_DELETE;
|
||||
case Marker::DELTA_VERTEX_SET_PROPERTY:
|
||||
return WalDeltaData::Type::VERTEX_SET_PROPERTY;
|
||||
case Marker::DELTA_EDGE_SET_PROPERTY:
|
||||
return WalDeltaData::Type::EDGE_SET_PROPERTY;
|
||||
case Marker::DELTA_TRANSACTION_END:
|
||||
return WalDeltaData::Type::TRANSACTION_END;
|
||||
case Marker::DELTA_LABEL_INDEX_CREATE:
|
||||
return WalDeltaData::Type::LABEL_INDEX_CREATE;
|
||||
case Marker::DELTA_LABEL_INDEX_DROP:
|
||||
return WalDeltaData::Type::LABEL_INDEX_DROP;
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
|
||||
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE;
|
||||
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
|
||||
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP;
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
|
||||
return WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE;
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
|
||||
return WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP;
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE;
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP;
|
||||
|
||||
case Marker::TYPE_NULL:
|
||||
case Marker::TYPE_BOOL:
|
||||
case Marker::TYPE_INT:
|
||||
case Marker::TYPE_DOUBLE:
|
||||
case Marker::TYPE_STRING:
|
||||
case Marker::TYPE_LIST:
|
||||
case Marker::TYPE_MAP:
|
||||
case Marker::TYPE_TEMPORAL_DATA:
|
||||
case Marker::TYPE_PROPERTY_VALUE:
|
||||
case Marker::SECTION_VERTEX:
|
||||
case Marker::SECTION_EDGE:
|
||||
case Marker::SECTION_MAPPER:
|
||||
case Marker::SECTION_METADATA:
|
||||
case Marker::SECTION_INDICES:
|
||||
case Marker::SECTION_CONSTRAINTS:
|
||||
case Marker::SECTION_DELTA:
|
||||
case Marker::SECTION_EPOCH_HISTORY:
|
||||
case Marker::SECTION_OFFSETS:
|
||||
case Marker::VALUE_FALSE:
|
||||
case Marker::VALUE_TRUE:
|
||||
throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
}
|
||||
|
||||
// Function used to either read or skip the current WAL delta data. The WAL
|
||||
// delta header must be read before calling this function. If the delta data is
|
||||
// read then the data returned is valid, if the delta data is skipped then the
|
||||
// returned data is not guaranteed to be set (it could be empty) and shouldn't
|
||||
// be used.
|
||||
// @throw RecoveryFailure
|
||||
template <bool read_data>
|
||||
WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
|
||||
WalDeltaData delta;
|
||||
|
||||
auto action = decoder->ReadMarker();
|
||||
if (!action) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.type = MarkerToWalDeltaDataType(*action);
|
||||
|
||||
switch (delta.type) {
|
||||
case WalDeltaData::Type::VERTEX_CREATE:
|
||||
case WalDeltaData::Type::VERTEX_DELETE: {
|
||||
auto gid = decoder->ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_create_delete.gid = Gid::FromUint(*gid);
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::VERTEX_ADD_LABEL:
|
||||
case WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
||||
auto gid = decoder->ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_add_remove_label.gid = Gid::FromUint(*gid);
|
||||
if constexpr (read_data) {
|
||||
auto label = decoder->ReadString();
|
||||
if (!label) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_add_remove_label.label = std::move(*label);
|
||||
} else {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::VERTEX_SET_PROPERTY:
|
||||
case WalDeltaData::Type::EDGE_SET_PROPERTY: {
|
||||
auto gid = decoder->ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_edge_set_property.gid = Gid::FromUint(*gid);
|
||||
if constexpr (read_data) {
|
||||
auto property = decoder->ReadString();
|
||||
if (!property) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_edge_set_property.property = std::move(*property);
|
||||
auto value = decoder->ReadPropertyValue();
|
||||
if (!value) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.vertex_edge_set_property.value = std::move(*value);
|
||||
} else {
|
||||
if (!decoder->SkipString() || !decoder->SkipPropertyValue()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EDGE_CREATE:
|
||||
case WalDeltaData::Type::EDGE_DELETE: {
|
||||
auto gid = decoder->ReadUint();
|
||||
if (!gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.edge_create_delete.gid = Gid::FromUint(*gid);
|
||||
if constexpr (read_data) {
|
||||
auto edge_type = decoder->ReadString();
|
||||
if (!edge_type) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.edge_create_delete.edge_type = std::move(*edge_type);
|
||||
} else {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
auto from_gid = decoder->ReadUint();
|
||||
if (!from_gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.edge_create_delete.from_vertex = Gid::FromUint(*from_gid);
|
||||
auto to_gid = decoder->ReadUint();
|
||||
if (!to_gid) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.edge_create_delete.to_vertex = Gid::FromUint(*to_gid);
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::TRANSACTION_END:
|
||||
break;
|
||||
case WalDeltaData::Type::LABEL_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_INDEX_DROP: {
|
||||
if constexpr (read_data) {
|
||||
auto label = decoder->ReadString();
|
||||
if (!label) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.operation_label.label = std::move(*label);
|
||||
} else {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
|
||||
if constexpr (read_data) {
|
||||
auto label = decoder->ReadString();
|
||||
if (!label) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.operation_label_property.label = std::move(*label);
|
||||
auto property = decoder->ReadString();
|
||||
if (!property) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.operation_label_property.property = std::move(*property);
|
||||
} else {
|
||||
if (!decoder->SkipString() || !decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
|
||||
if constexpr (read_data) {
|
||||
auto label = decoder->ReadString();
|
||||
if (!label) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.operation_label_properties.label = std::move(*label);
|
||||
auto properties_count = decoder->ReadUint();
|
||||
if (!properties_count) throw RecoveryFailure("Invalid WAL data!");
|
||||
for (uint64_t i = 0; i < *properties_count; ++i) {
|
||||
auto property = decoder->ReadString();
|
||||
if (!property) throw RecoveryFailure("Invalid WAL data!");
|
||||
delta.operation_label_properties.properties.emplace(std::move(*property));
|
||||
}
|
||||
} else {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
auto properties_count = decoder->ReadUint();
|
||||
if (!properties_count) throw RecoveryFailure("Invalid WAL data!");
|
||||
for (uint64_t i = 0; i < *properties_count; ++i) {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Function used to read information about the WAL file.
|
||||
WalInfo ReadWalInfo(const std::filesystem::path &path) {
|
||||
// Check magic and version.
|
||||
Decoder wal;
|
||||
auto version = wal.Initialize(path, kWalMagic);
|
||||
if (!version) throw RecoveryFailure("Couldn't read WAL magic and/or version!");
|
||||
if (!IsVersionSupported(*version)) throw RecoveryFailure("Invalid WAL version!");
|
||||
|
||||
// Prepare return value.
|
||||
WalInfo info;
|
||||
|
||||
// Read offsets.
|
||||
{
|
||||
auto marker = wal.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_OFFSETS) throw RecoveryFailure("Invalid WAL data!");
|
||||
|
||||
auto wal_size = wal.GetSize();
|
||||
if (!wal_size) throw RecoveryFailure("Invalid WAL data!");
|
||||
|
||||
auto read_offset = [&wal, wal_size] {
|
||||
auto maybe_offset = wal.ReadUint();
|
||||
if (!maybe_offset) throw RecoveryFailure("Invalid WAL format!");
|
||||
auto offset = *maybe_offset;
|
||||
if (offset > *wal_size) throw RecoveryFailure("Invalid WAL format!");
|
||||
return offset;
|
||||
};
|
||||
|
||||
info.offset_metadata = read_offset();
|
||||
info.offset_deltas = read_offset();
|
||||
}
|
||||
|
||||
// Read metadata.
|
||||
{
|
||||
wal.SetPosition(info.offset_metadata);
|
||||
|
||||
auto marker = wal.ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_METADATA) throw RecoveryFailure("Invalid WAL data!");
|
||||
|
||||
auto maybe_uuid = wal.ReadString();
|
||||
if (!maybe_uuid) throw RecoveryFailure("Invalid WAL data!");
|
||||
info.uuid = std::move(*maybe_uuid);
|
||||
|
||||
auto maybe_epoch_id = wal.ReadString();
|
||||
if (!maybe_epoch_id) throw RecoveryFailure("Invalid WAL data!");
|
||||
info.epoch_id = std::move(*maybe_epoch_id);
|
||||
|
||||
auto maybe_seq_num = wal.ReadUint();
|
||||
if (!maybe_seq_num) throw RecoveryFailure("Invalid WAL data!");
|
||||
info.seq_num = *maybe_seq_num;
|
||||
}
|
||||
|
||||
// Read deltas.
|
||||
info.num_deltas = 0;
|
||||
auto validate_delta = [&wal]() -> std::optional<std::pair<uint64_t, bool>> {
|
||||
try {
|
||||
auto timestamp = ReadWalDeltaHeader(&wal);
|
||||
auto type = SkipWalDeltaData(&wal);
|
||||
return {{timestamp, IsWalDeltaDataTypeTransactionEnd(type)}};
|
||||
} catch (const RecoveryFailure &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
auto size = wal.GetSize();
|
||||
// Here we read the whole file and determine the number of valid deltas. A
|
||||
// delta is valid only if all of its data can be successfully read. This
|
||||
// allows us to recover data from WAL files that are corrupt at the end (eg.
|
||||
// because of power loss) but are still valid at the beginning. While reading
|
||||
// the deltas we only count deltas which are a part of a fully valid
|
||||
// transaction (indicated by a TRANSACTION_END delta or any other
|
||||
// non-transactional operation).
|
||||
std::optional<uint64_t> current_timestamp;
|
||||
uint64_t num_deltas = 0;
|
||||
while (wal.GetPosition() != size) {
|
||||
auto ret = validate_delta();
|
||||
if (!ret) break;
|
||||
auto [timestamp, is_end_of_transaction] = *ret;
|
||||
if (!current_timestamp) current_timestamp = timestamp;
|
||||
if (*current_timestamp != timestamp) break;
|
||||
++num_deltas;
|
||||
if (is_end_of_transaction) {
|
||||
if (info.num_deltas == 0) {
|
||||
info.from_timestamp = timestamp;
|
||||
info.to_timestamp = timestamp;
|
||||
}
|
||||
if (timestamp < info.from_timestamp || timestamp < info.to_timestamp) break;
|
||||
info.to_timestamp = timestamp;
|
||||
info.num_deltas += num_deltas;
|
||||
current_timestamp = std::nullopt;
|
||||
num_deltas = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.num_deltas == 0) throw RecoveryFailure("Invalid WAL data!");
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool operator==(const WalDeltaData &a, const WalDeltaData &b) {
|
||||
if (a.type != b.type) return false;
|
||||
switch (a.type) {
|
||||
case WalDeltaData::Type::VERTEX_CREATE:
|
||||
case WalDeltaData::Type::VERTEX_DELETE:
|
||||
return a.vertex_create_delete.gid == b.vertex_create_delete.gid;
|
||||
|
||||
case WalDeltaData::Type::VERTEX_ADD_LABEL:
|
||||
case WalDeltaData::Type::VERTEX_REMOVE_LABEL:
|
||||
return a.vertex_add_remove_label.gid == b.vertex_add_remove_label.gid &&
|
||||
a.vertex_add_remove_label.label == b.vertex_add_remove_label.label;
|
||||
|
||||
case WalDeltaData::Type::VERTEX_SET_PROPERTY:
|
||||
case WalDeltaData::Type::EDGE_SET_PROPERTY:
|
||||
return a.vertex_edge_set_property.gid == b.vertex_edge_set_property.gid &&
|
||||
a.vertex_edge_set_property.property == b.vertex_edge_set_property.property &&
|
||||
a.vertex_edge_set_property.value == b.vertex_edge_set_property.value;
|
||||
|
||||
case WalDeltaData::Type::EDGE_CREATE:
|
||||
case WalDeltaData::Type::EDGE_DELETE:
|
||||
return a.edge_create_delete.gid == b.edge_create_delete.gid &&
|
||||
a.edge_create_delete.edge_type == b.edge_create_delete.edge_type &&
|
||||
a.edge_create_delete.from_vertex == b.edge_create_delete.from_vertex &&
|
||||
a.edge_create_delete.to_vertex == b.edge_create_delete.to_vertex;
|
||||
|
||||
case WalDeltaData::Type::TRANSACTION_END:
|
||||
return true;
|
||||
|
||||
case WalDeltaData::Type::LABEL_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_INDEX_DROP:
|
||||
return a.operation_label.label == b.operation_label.label;
|
||||
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
|
||||
return a.operation_label_property.label == b.operation_label_property.label &&
|
||||
a.operation_label_property.property == b.operation_label_property.property;
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
|
||||
return a.operation_label_properties.label == b.operation_label_properties.label &&
|
||||
a.operation_label_properties.properties == b.operation_label_properties.properties;
|
||||
}
|
||||
}
|
||||
bool operator!=(const WalDeltaData &a, const WalDeltaData &b) { return !(a == b); }
|
||||
|
||||
// Function used to read the WAL delta header. The function returns the delta
|
||||
// timestamp.
|
||||
uint64_t ReadWalDeltaHeader(BaseDecoder *decoder) {
|
||||
auto marker = decoder->ReadMarker();
|
||||
if (!marker || *marker != Marker::SECTION_DELTA) throw RecoveryFailure("Invalid WAL data!");
|
||||
|
||||
auto timestamp = decoder->ReadUint();
|
||||
if (!timestamp) throw RecoveryFailure("Invalid WAL data!");
|
||||
return *timestamp;
|
||||
}
|
||||
|
||||
// Function used to read the current WAL delta data. The WAL delta header must
|
||||
// be read before calling this function.
|
||||
WalDeltaData ReadWalDeltaData(BaseDecoder *decoder) { return ReadSkipWalDeltaData<true>(decoder); }
|
||||
|
||||
// Function used to skip the current WAL delta data. The WAL delta header must
|
||||
// be read before calling this function.
|
||||
WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder) {
|
||||
auto delta = ReadSkipWalDeltaData<false>(decoder);
|
||||
return delta.type;
|
||||
}
|
||||
|
||||
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta,
|
||||
const Vertex &vertex, uint64_t timestamp) {
|
||||
// When converting a Delta to a WAL delta the logic is inverted. That is
|
||||
// because the Delta's represent undo actions and we want to store redo
|
||||
// actions.
|
||||
encoder->WriteMarker(Marker::SECTION_DELTA);
|
||||
encoder->WriteUint(timestamp);
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
switch (delta.action) {
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
encoder->WriteMarker(VertexActionToMarker(delta.action));
|
||||
encoder->WriteUint(vertex.gid.AsUint());
|
||||
break;
|
||||
}
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
encoder->WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY);
|
||||
encoder->WriteUint(vertex.gid.AsUint());
|
||||
encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint()));
|
||||
// The property value is the value that is currently stored in the
|
||||
// vertex.
|
||||
// TODO (mferencevic): Mitigate the memory allocation introduced here
|
||||
// (with the `GetProperty` call). It is the only memory allocation in the
|
||||
// entire WAL file writing logic.
|
||||
encoder->WritePropertyValue(vertex.properties.GetProperty(delta.property.key));
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL: {
|
||||
encoder->WriteMarker(VertexActionToMarker(delta.action));
|
||||
encoder->WriteUint(vertex.gid.AsUint());
|
||||
encoder->WriteString(name_id_mapper->IdToName(delta.label.AsUint()));
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE: {
|
||||
encoder->WriteMarker(VertexActionToMarker(delta.action));
|
||||
if (items.properties_on_edges) {
|
||||
encoder->WriteUint(delta.vertex_edge.edge.ptr->gid.AsUint());
|
||||
} else {
|
||||
encoder->WriteUint(delta.vertex_edge.edge.gid.AsUint());
|
||||
}
|
||||
encoder->WriteString(name_id_mapper->IdToName(delta.vertex_edge.edge_type.AsUint()));
|
||||
encoder->WriteUint(vertex.gid.AsUint());
|
||||
encoder->WriteUint(delta.vertex_edge.vertex->gid.AsUint());
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
// These actions are already encoded in the *_OUT_EDGE actions. This
|
||||
// function should never be called for this type of deltas.
|
||||
LOG_FATAL("Invalid delta action!");
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta &delta, const Edge &edge,
|
||||
uint64_t timestamp) {
|
||||
// When converting a Delta to a WAL delta the logic is inverted. That is
|
||||
// because the Delta's represent undo actions and we want to store redo
|
||||
// actions.
|
||||
encoder->WriteMarker(Marker::SECTION_DELTA);
|
||||
encoder->WriteUint(timestamp);
|
||||
std::lock_guard<utils::SpinLock> guard(edge.lock);
|
||||
switch (delta.action) {
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
encoder->WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY);
|
||||
encoder->WriteUint(edge.gid.AsUint());
|
||||
encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint()));
|
||||
// The property value is the value that is currently stored in the
|
||||
// edge.
|
||||
// TODO (mferencevic): Mitigate the memory allocation introduced here
|
||||
// (with the `GetProperty` call). It is the only memory allocation in the
|
||||
// entire WAL file writing logic.
|
||||
encoder->WritePropertyValue(edge.properties.GetProperty(delta.property.key));
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
// These actions are already encoded in vertex *_OUT_EDGE actions. Also,
|
||||
// these deltas don't contain any information about the from vertex, to
|
||||
// vertex or edge type so they are useless. This function should never
|
||||
// be called for this type of deltas.
|
||||
LOG_FATAL("Invalid delta action!");
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
// These deltas shouldn't appear for edges.
|
||||
LOG_FATAL("Invalid database state!");
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp) {
|
||||
encoder->WriteMarker(Marker::SECTION_DELTA);
|
||||
encoder->WriteUint(timestamp);
|
||||
encoder->WriteMarker(Marker::DELTA_TRANSACTION_END);
|
||||
}
|
||||
|
||||
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
|
||||
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp) {
|
||||
encoder->WriteMarker(Marker::SECTION_DELTA);
|
||||
encoder->WriteUint(timestamp);
|
||||
switch (operation) {
|
||||
case StorageGlobalOperation::LABEL_INDEX_CREATE:
|
||||
case StorageGlobalOperation::LABEL_INDEX_DROP: {
|
||||
MG_ASSERT(properties.empty(), "Invalid function call!");
|
||||
encoder->WriteMarker(OperationToMarker(operation));
|
||||
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
|
||||
break;
|
||||
}
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE:
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP:
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE:
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: {
|
||||
MG_ASSERT(properties.size() == 1, "Invalid function call!");
|
||||
encoder->WriteMarker(OperationToMarker(operation));
|
||||
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
|
||||
encoder->WriteString(name_id_mapper->IdToName((*properties.begin()).AsUint()));
|
||||
break;
|
||||
}
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE:
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: {
|
||||
MG_ASSERT(!properties.empty(), "Invalid function call!");
|
||||
encoder->WriteMarker(OperationToMarker(operation));
|
||||
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
|
||||
encoder->WriteUint(properties.size());
|
||||
for (const auto &property : properties) {
|
||||
encoder->WriteString(name_id_mapper->IdToName(property.AsUint()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
|
||||
const std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices,
|
||||
utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
|
||||
Config::Items items) {
|
||||
spdlog::info("Trying to load WAL file {}.", path);
|
||||
RecoveryInfo ret;
|
||||
|
||||
Decoder wal;
|
||||
auto version = wal.Initialize(path, kWalMagic);
|
||||
if (!version) throw RecoveryFailure("Couldn't read WAL magic and/or version!");
|
||||
if (!IsVersionSupported(*version)) throw RecoveryFailure("Invalid WAL version!");
|
||||
|
||||
// Read wal info.
|
||||
auto info = ReadWalInfo(path);
|
||||
ret.last_commit_timestamp = info.to_timestamp;
|
||||
|
||||
// Check timestamp.
|
||||
if (last_loaded_timestamp && info.to_timestamp <= *last_loaded_timestamp) {
|
||||
spdlog::info("Skip loading WAL file because it is too old.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Recover deltas.
|
||||
wal.SetPosition(info.offset_deltas);
|
||||
uint64_t deltas_applied = 0;
|
||||
auto edge_acc = edges->access();
|
||||
auto vertex_acc = vertices->access();
|
||||
spdlog::info("WAL file contains {} deltas.", info.num_deltas);
|
||||
for (uint64_t i = 0; i < info.num_deltas; ++i) {
|
||||
// Read WAL delta header to find out the delta timestamp.
|
||||
auto timestamp = ReadWalDeltaHeader(&wal);
|
||||
|
||||
if (!last_loaded_timestamp || timestamp > *last_loaded_timestamp) {
|
||||
// This delta should be loaded.
|
||||
auto delta = ReadWalDeltaData(&wal);
|
||||
switch (delta.type) {
|
||||
case WalDeltaData::Type::VERTEX_CREATE: {
|
||||
auto [vertex, inserted] = vertex_acc.insert(Vertex{delta.vertex_create_delete.gid, nullptr});
|
||||
if (!inserted) throw RecoveryFailure("The vertex must be inserted here!");
|
||||
|
||||
ret.next_vertex_id = std::max(ret.next_vertex_id, delta.vertex_create_delete.gid.AsUint() + 1);
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::VERTEX_DELETE: {
|
||||
auto vertex = vertex_acc.find(delta.vertex_create_delete.gid);
|
||||
if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!");
|
||||
if (!vertex->in_edges.empty() || !vertex->out_edges.empty())
|
||||
throw RecoveryFailure("The vertex can't be deleted because it still has edges!");
|
||||
|
||||
if (!vertex_acc.remove(delta.vertex_create_delete.gid))
|
||||
throw RecoveryFailure("The vertex must be removed here!");
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::VERTEX_ADD_LABEL:
|
||||
case WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
||||
auto vertex = vertex_acc.find(delta.vertex_add_remove_label.gid);
|
||||
if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!");
|
||||
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.vertex_add_remove_label.label));
|
||||
auto it = std::find(vertex->labels.begin(), vertex->labels.end(), label_id);
|
||||
|
||||
if (delta.type == WalDeltaData::Type::VERTEX_ADD_LABEL) {
|
||||
if (it != vertex->labels.end()) throw RecoveryFailure("The vertex already has the label!");
|
||||
vertex->labels.push_back(label_id);
|
||||
} else {
|
||||
if (it == vertex->labels.end()) throw RecoveryFailure("The vertex doesn't have the label!");
|
||||
std::swap(*it, vertex->labels.back());
|
||||
vertex->labels.pop_back();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::VERTEX_SET_PROPERTY: {
|
||||
auto vertex = vertex_acc.find(delta.vertex_edge_set_property.gid);
|
||||
if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!");
|
||||
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property));
|
||||
auto &property_value = delta.vertex_edge_set_property.value;
|
||||
|
||||
vertex->properties.SetProperty(property_id, property_value);
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EDGE_CREATE: {
|
||||
auto from_vertex = vertex_acc.find(delta.edge_create_delete.from_vertex);
|
||||
if (from_vertex == vertex_acc.end()) throw RecoveryFailure("The from vertex doesn't exist!");
|
||||
auto to_vertex = vertex_acc.find(delta.edge_create_delete.to_vertex);
|
||||
if (to_vertex == vertex_acc.end()) throw RecoveryFailure("The to vertex doesn't exist!");
|
||||
|
||||
auto edge_gid = delta.edge_create_delete.gid;
|
||||
auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type));
|
||||
EdgeRef edge_ref(edge_gid);
|
||||
if (items.properties_on_edges) {
|
||||
auto [edge, inserted] = edge_acc.insert(Edge{edge_gid, nullptr});
|
||||
if (!inserted) throw RecoveryFailure("The edge must be inserted here!");
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
}
|
||||
{
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{edge_type_id, &*to_vertex, edge_ref};
|
||||
auto it = std::find(from_vertex->out_edges.begin(), from_vertex->out_edges.end(), link);
|
||||
if (it != from_vertex->out_edges.end()) throw RecoveryFailure("The from vertex already has this edge!");
|
||||
from_vertex->out_edges.push_back(link);
|
||||
}
|
||||
{
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{edge_type_id, &*from_vertex, edge_ref};
|
||||
auto it = std::find(to_vertex->in_edges.begin(), to_vertex->in_edges.end(), link);
|
||||
if (it != to_vertex->in_edges.end()) throw RecoveryFailure("The to vertex already has this edge!");
|
||||
to_vertex->in_edges.push_back(link);
|
||||
}
|
||||
|
||||
ret.next_edge_id = std::max(ret.next_edge_id, edge_gid.AsUint() + 1);
|
||||
|
||||
// Increment edge count.
|
||||
edge_count->fetch_add(1, std::memory_order_acq_rel);
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EDGE_DELETE: {
|
||||
auto from_vertex = vertex_acc.find(delta.edge_create_delete.from_vertex);
|
||||
if (from_vertex == vertex_acc.end()) throw RecoveryFailure("The from vertex doesn't exist!");
|
||||
auto to_vertex = vertex_acc.find(delta.edge_create_delete.to_vertex);
|
||||
if (to_vertex == vertex_acc.end()) throw RecoveryFailure("The to vertex doesn't exist!");
|
||||
|
||||
auto edge_gid = delta.edge_create_delete.gid;
|
||||
auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type));
|
||||
EdgeRef edge_ref(edge_gid);
|
||||
if (items.properties_on_edges) {
|
||||
auto edge = edge_acc.find(edge_gid);
|
||||
if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!");
|
||||
edge_ref = EdgeRef(&*edge);
|
||||
}
|
||||
{
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{edge_type_id, &*to_vertex, edge_ref};
|
||||
auto it = std::find(from_vertex->out_edges.begin(), from_vertex->out_edges.end(), link);
|
||||
if (it == from_vertex->out_edges.end()) throw RecoveryFailure("The from vertex doesn't have this edge!");
|
||||
std::swap(*it, from_vertex->out_edges.back());
|
||||
from_vertex->out_edges.pop_back();
|
||||
}
|
||||
{
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{edge_type_id, &*from_vertex, edge_ref};
|
||||
auto it = std::find(to_vertex->in_edges.begin(), to_vertex->in_edges.end(), link);
|
||||
if (it == to_vertex->in_edges.end()) throw RecoveryFailure("The to vertex doesn't have this edge!");
|
||||
std::swap(*it, to_vertex->in_edges.back());
|
||||
to_vertex->in_edges.pop_back();
|
||||
}
|
||||
if (items.properties_on_edges) {
|
||||
if (!edge_acc.remove(edge_gid)) throw RecoveryFailure("The edge must be removed here!");
|
||||
}
|
||||
|
||||
// Decrement edge count.
|
||||
edge_count->fetch_add(-1, std::memory_order_acq_rel);
|
||||
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EDGE_SET_PROPERTY: {
|
||||
if (!items.properties_on_edges)
|
||||
throw RecoveryFailure(
|
||||
"The WAL has properties on edges, but the storage is "
|
||||
"configured without properties on edges!");
|
||||
auto edge = edge_acc.find(delta.vertex_edge_set_property.gid);
|
||||
if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!");
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property));
|
||||
auto &property_value = delta.vertex_edge_set_property.value;
|
||||
edge->properties.SetProperty(property_id, property_value);
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::TRANSACTION_END:
|
||||
break;
|
||||
case WalDeltaData::Type::LABEL_INDEX_CREATE: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label));
|
||||
AddRecoveredIndexConstraint(&indices_constraints->indices.label, label_id, "The label index already exists!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::LABEL_INDEX_DROP: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label));
|
||||
RemoveRecoveredIndexConstraint(&indices_constraints->indices.label, label_id,
|
||||
"The label index doesn't exist!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
|
||||
AddRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id},
|
||||
"The label property index already exists!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
|
||||
RemoveRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id},
|
||||
"The label property index doesn't exist!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
|
||||
AddRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id},
|
||||
"The existence constraint already exists!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
|
||||
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
|
||||
RemoveRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id},
|
||||
"The existence constraint doesn't exist!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label));
|
||||
std::set<PropertyId> property_ids;
|
||||
for (const auto &prop : delta.operation_label_properties.properties) {
|
||||
property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop)));
|
||||
}
|
||||
AddRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids},
|
||||
"The unique constraint already exists!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
|
||||
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label));
|
||||
std::set<PropertyId> property_ids;
|
||||
for (const auto &prop : delta.operation_label_properties.properties) {
|
||||
property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop)));
|
||||
}
|
||||
RemoveRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids},
|
||||
"The unique constraint doesn't exist!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret.next_timestamp = std::max(ret.next_timestamp, timestamp + 1);
|
||||
++deltas_applied;
|
||||
} else {
|
||||
// This delta should be skipped.
|
||||
SkipWalDeltaData(&wal);
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("Applied {} deltas from WAL. Skipped {} deltas, because they were too old.", deltas_applied,
|
||||
info.num_deltas - deltas_applied);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
WalFile::WalFile(const std::filesystem::path &wal_directory, const std::string_view uuid,
|
||||
const std::string_view epoch_id, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
|
||||
utils::FileRetainer *file_retainer)
|
||||
: items_(items),
|
||||
name_id_mapper_(name_id_mapper),
|
||||
path_(wal_directory / MakeWalName()),
|
||||
from_timestamp_(0),
|
||||
to_timestamp_(0),
|
||||
count_(0),
|
||||
seq_num_(seq_num),
|
||||
file_retainer_(file_retainer) {
|
||||
// Ensure that the storage directory exists.
|
||||
utils::EnsureDirOrDie(wal_directory);
|
||||
|
||||
// Initialize the WAL file.
|
||||
wal_.Initialize(path_, kWalMagic, kVersion);
|
||||
|
||||
// Write placeholder offsets.
|
||||
uint64_t offset_offsets = 0;
|
||||
uint64_t offset_metadata = 0;
|
||||
uint64_t offset_deltas = 0;
|
||||
wal_.WriteMarker(Marker::SECTION_OFFSETS);
|
||||
offset_offsets = wal_.GetPosition();
|
||||
wal_.WriteUint(offset_metadata);
|
||||
wal_.WriteUint(offset_deltas);
|
||||
|
||||
// Write metadata.
|
||||
offset_metadata = wal_.GetPosition();
|
||||
wal_.WriteMarker(Marker::SECTION_METADATA);
|
||||
wal_.WriteString(uuid);
|
||||
wal_.WriteString(epoch_id);
|
||||
wal_.WriteUint(seq_num);
|
||||
|
||||
// Write final offsets.
|
||||
offset_deltas = wal_.GetPosition();
|
||||
wal_.SetPosition(offset_offsets);
|
||||
wal_.WriteUint(offset_metadata);
|
||||
wal_.WriteUint(offset_deltas);
|
||||
wal_.SetPosition(offset_deltas);
|
||||
|
||||
// Sync the initial data.
|
||||
wal_.Sync();
|
||||
}
|
||||
|
||||
WalFile::WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper,
|
||||
uint64_t seq_num, uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count,
|
||||
utils::FileRetainer *file_retainer)
|
||||
: items_(items),
|
||||
name_id_mapper_(name_id_mapper),
|
||||
path_(std::move(current_wal_path)),
|
||||
from_timestamp_(from_timestamp),
|
||||
to_timestamp_(to_timestamp),
|
||||
count_(count),
|
||||
seq_num_(seq_num),
|
||||
file_retainer_(file_retainer) {
|
||||
wal_.OpenExisting(path_);
|
||||
}
|
||||
|
||||
void WalFile::FinalizeWal() {
|
||||
if (count_ != 0) {
|
||||
wal_.Finalize();
|
||||
// Rename file.
|
||||
std::filesystem::path new_path(path_);
|
||||
new_path.replace_filename(RemakeWalName(path_.filename(), from_timestamp_, to_timestamp_));
|
||||
|
||||
utils::CopyFile(path_, new_path);
|
||||
wal_.Close();
|
||||
file_retainer_->DeleteFile(path_);
|
||||
path_ = std::move(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
void WalFile::DeleteWal() {
|
||||
wal_.Close();
|
||||
file_retainer_->DeleteFile(path_);
|
||||
}
|
||||
|
||||
WalFile::~WalFile() {
|
||||
if (count_ == 0) {
|
||||
// Remove empty WAL file.
|
||||
utils::DeleteFile(path_);
|
||||
}
|
||||
}
|
||||
|
||||
void WalFile::AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp) {
|
||||
EncodeDelta(&wal_, name_id_mapper_, items_, delta, vertex, timestamp);
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp) {
|
||||
EncodeDelta(&wal_, name_id_mapper_, delta, edge, timestamp);
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::AppendTransactionEnd(uint64_t timestamp) {
|
||||
EncodeTransactionEnd(&wal_, timestamp);
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t timestamp) {
|
||||
EncodeOperation(&wal_, name_id_mapper_, operation, label, properties, timestamp);
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::Sync() { wal_.Sync(); }
|
||||
|
||||
uint64_t WalFile::GetSize() { return wal_.GetSize(); }
|
||||
|
||||
uint64_t WalFile::SequenceNumber() const { return seq_num_; }
|
||||
|
||||
void WalFile::UpdateStats(uint64_t timestamp) {
|
||||
if (count_ == 0) from_timestamp_ = timestamp;
|
||||
to_timestamp_ = timestamp;
|
||||
count_ += 1;
|
||||
}
|
||||
|
||||
void WalFile::DisableFlushing() { wal_.DisableFlushing(); }
|
||||
|
||||
void WalFile::EnableFlushing() { wal_.EnableFlushing(); }
|
||||
|
||||
void WalFile::TryFlushing() { wal_.TryFlushing(); }
|
||||
|
||||
std::pair<const uint8_t *, size_t> WalFile::CurrentFileBuffer() const { return wal_.CurrentFileBuffer(); }
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -1,261 +0,0 @@
|
||||
// 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 <cstdint>
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/delta.hpp"
|
||||
#include "storage/v3/durability/metadata.hpp"
|
||||
#include "storage/v3/durability/serialization.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::durability {
|
||||
|
||||
/// Structure used to hold information about a WAL.
|
||||
struct WalInfo {
|
||||
uint64_t offset_metadata;
|
||||
uint64_t offset_deltas;
|
||||
|
||||
std::string uuid;
|
||||
std::string epoch_id;
|
||||
uint64_t seq_num;
|
||||
uint64_t from_timestamp;
|
||||
uint64_t to_timestamp;
|
||||
uint64_t num_deltas;
|
||||
};
|
||||
|
||||
/// Structure used to return loaded WAL delta data.
|
||||
struct WalDeltaData {
|
||||
enum class Type {
|
||||
VERTEX_CREATE,
|
||||
VERTEX_DELETE,
|
||||
VERTEX_ADD_LABEL,
|
||||
VERTEX_REMOVE_LABEL,
|
||||
VERTEX_SET_PROPERTY,
|
||||
EDGE_CREATE,
|
||||
EDGE_DELETE,
|
||||
EDGE_SET_PROPERTY,
|
||||
TRANSACTION_END,
|
||||
LABEL_INDEX_CREATE,
|
||||
LABEL_INDEX_DROP,
|
||||
LABEL_PROPERTY_INDEX_CREATE,
|
||||
LABEL_PROPERTY_INDEX_DROP,
|
||||
EXISTENCE_CONSTRAINT_CREATE,
|
||||
EXISTENCE_CONSTRAINT_DROP,
|
||||
UNIQUE_CONSTRAINT_CREATE,
|
||||
UNIQUE_CONSTRAINT_DROP,
|
||||
};
|
||||
|
||||
Type type{Type::TRANSACTION_END};
|
||||
|
||||
struct {
|
||||
Gid gid;
|
||||
} vertex_create_delete;
|
||||
|
||||
struct {
|
||||
Gid gid;
|
||||
std::string label;
|
||||
} vertex_add_remove_label;
|
||||
|
||||
struct {
|
||||
Gid gid;
|
||||
std::string property;
|
||||
PropertyValue value;
|
||||
} vertex_edge_set_property;
|
||||
|
||||
struct {
|
||||
Gid gid;
|
||||
std::string edge_type;
|
||||
Gid from_vertex;
|
||||
Gid to_vertex;
|
||||
} edge_create_delete;
|
||||
|
||||
struct {
|
||||
std::string label;
|
||||
} operation_label;
|
||||
|
||||
struct {
|
||||
std::string label;
|
||||
std::string property;
|
||||
} operation_label_property;
|
||||
|
||||
struct {
|
||||
std::string label;
|
||||
std::set<std::string> properties;
|
||||
} operation_label_properties;
|
||||
};
|
||||
|
||||
bool operator==(const WalDeltaData &a, const WalDeltaData &b);
|
||||
bool operator!=(const WalDeltaData &a, const WalDeltaData &b);
|
||||
|
||||
/// Enum used to indicate a global database operation that isn't transactional.
|
||||
enum class StorageGlobalOperation {
|
||||
LABEL_INDEX_CREATE,
|
||||
LABEL_INDEX_DROP,
|
||||
LABEL_PROPERTY_INDEX_CREATE,
|
||||
LABEL_PROPERTY_INDEX_DROP,
|
||||
EXISTENCE_CONSTRAINT_CREATE,
|
||||
EXISTENCE_CONSTRAINT_DROP,
|
||||
UNIQUE_CONSTRAINT_CREATE,
|
||||
UNIQUE_CONSTRAINT_DROP,
|
||||
};
|
||||
|
||||
constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
|
||||
switch (type) {
|
||||
// These delta actions are all found inside transactions so they don't
|
||||
// indicate a transaction end.
|
||||
case WalDeltaData::Type::VERTEX_CREATE:
|
||||
case WalDeltaData::Type::VERTEX_DELETE:
|
||||
case WalDeltaData::Type::VERTEX_ADD_LABEL:
|
||||
case WalDeltaData::Type::VERTEX_REMOVE_LABEL:
|
||||
case WalDeltaData::Type::EDGE_CREATE:
|
||||
case WalDeltaData::Type::EDGE_DELETE:
|
||||
case WalDeltaData::Type::VERTEX_SET_PROPERTY:
|
||||
case WalDeltaData::Type::EDGE_SET_PROPERTY:
|
||||
return false;
|
||||
|
||||
// This delta explicitly indicates that a transaction is done.
|
||||
case WalDeltaData::Type::TRANSACTION_END:
|
||||
// These operations aren't transactional and they are encoded only using
|
||||
// a single delta, so they each individually mark the end of their
|
||||
// 'transaction'.
|
||||
case WalDeltaData::Type::LABEL_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_INDEX_DROP:
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
|
||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Function used to read information about the WAL file.
|
||||
/// @throw RecoveryFailure
|
||||
WalInfo ReadWalInfo(const std::filesystem::path &path);
|
||||
|
||||
/// Function used to read the WAL delta header. The function returns the delta
|
||||
/// timestamp.
|
||||
/// @throw RecoveryFailure
|
||||
uint64_t ReadWalDeltaHeader(BaseDecoder *decoder);
|
||||
|
||||
/// Function used to read the current WAL delta data. The function returns the
|
||||
/// read delta data. The WAL delta header must be read before calling this
|
||||
/// function.
|
||||
/// @throw RecoveryFailure
|
||||
WalDeltaData ReadWalDeltaData(BaseDecoder *decoder);
|
||||
|
||||
/// Function used to skip the current WAL delta data. The function returns the
|
||||
/// skipped delta type. The WAL delta header must be read before calling this
|
||||
/// function.
|
||||
/// @throw RecoveryFailure
|
||||
WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder);
|
||||
|
||||
/// Function used to encode a `Delta` that originated from a `Vertex`.
|
||||
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta,
|
||||
const Vertex &vertex, uint64_t timestamp);
|
||||
|
||||
/// Function used to encode a `Delta` that originated from an `Edge`.
|
||||
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta &delta, const Edge &edge,
|
||||
uint64_t timestamp);
|
||||
|
||||
/// Function used to encode the transaction end.
|
||||
void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp);
|
||||
|
||||
/// Function used to encode non-transactional operation.
|
||||
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
|
||||
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp);
|
||||
|
||||
/// Function used to load the WAL data into the storage.
|
||||
/// @throw RecoveryFailure
|
||||
RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
|
||||
std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices,
|
||||
utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
|
||||
Config::Items items);
|
||||
|
||||
/// WalFile class used to append deltas and operations to the WAL file.
|
||||
class WalFile {
|
||||
public:
|
||||
WalFile(const std::filesystem::path &wal_directory, std::string_view uuid, std::string_view epoch_id,
|
||||
Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, utils::FileRetainer *file_retainer);
|
||||
WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
|
||||
uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, utils::FileRetainer *file_retainer);
|
||||
|
||||
WalFile(const WalFile &) = delete;
|
||||
WalFile(WalFile &&) = delete;
|
||||
WalFile &operator=(const WalFile &) = delete;
|
||||
WalFile &operator=(WalFile &&) = delete;
|
||||
|
||||
~WalFile();
|
||||
|
||||
void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp);
|
||||
void AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp);
|
||||
|
||||
void AppendTransactionEnd(uint64_t timestamp);
|
||||
|
||||
void AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t timestamp);
|
||||
|
||||
void Sync();
|
||||
|
||||
uint64_t GetSize();
|
||||
|
||||
uint64_t SequenceNumber() const;
|
||||
|
||||
auto FromTimestamp() const { return from_timestamp_; }
|
||||
|
||||
auto ToTimestamp() const { return to_timestamp_; }
|
||||
|
||||
auto Count() const { return count_; }
|
||||
|
||||
// Disable flushing of the internal buffer.
|
||||
void DisableFlushing();
|
||||
// Enable flushing of the internal buffer.
|
||||
void EnableFlushing();
|
||||
// Try flushing the internal buffer.
|
||||
void TryFlushing();
|
||||
// Get the internal buffer with its size.
|
||||
std::pair<const uint8_t *, size_t> CurrentFileBuffer() const;
|
||||
|
||||
// Get the path of the current WAL file.
|
||||
const auto &Path() const { return path_; }
|
||||
|
||||
void FinalizeWal();
|
||||
void DeleteWal();
|
||||
|
||||
private:
|
||||
void UpdateStats(uint64_t timestamp);
|
||||
|
||||
Config::Items items_;
|
||||
NameIdMapper *name_id_mapper_;
|
||||
Encoder wal_;
|
||||
std::filesystem::path path_;
|
||||
uint64_t from_timestamp_;
|
||||
uint64_t to_timestamp_;
|
||||
uint64_t count_;
|
||||
uint64_t seq_num_;
|
||||
|
||||
utils::FileRetainer *file_retainer_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
@ -33,7 +33,6 @@ struct Edge {
|
||||
|
||||
PropertyStore properties;
|
||||
|
||||
mutable utils::SpinLock lock;
|
||||
bool deleted;
|
||||
// uint8_t PAD;
|
||||
// uint16_t PAD;
|
||||
|
@ -22,14 +22,10 @@
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
bool EdgeAccessor::IsVisible(const View view) const {
|
||||
bool deleted = true;
|
||||
bool exists = true;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
deleted = edge_.ptr->deleted;
|
||||
delta = edge_.ptr->delta;
|
||||
}
|
||||
auto deleted = edge_.ptr->deleted;
|
||||
auto exists = true;
|
||||
auto *delta = edge_.ptr->delta;
|
||||
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
@ -54,20 +50,14 @@ bool EdgeAccessor::IsVisible(const View view) const {
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
VertexAccessor EdgeAccessor::FromVertex() const {
|
||||
return {from_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_};
|
||||
}
|
||||
const VertexId &EdgeAccessor::FromVertex() const { return from_vertex_; }
|
||||
|
||||
VertexAccessor EdgeAccessor::ToVertex() const {
|
||||
return {to_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_};
|
||||
}
|
||||
const VertexId &EdgeAccessor::ToVertex() const { return to_vertex_; }
|
||||
|
||||
Result<PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||
@ -88,8 +78,6 @@ Result<PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const Prope
|
||||
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
|
||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||
@ -104,18 +92,17 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
Result<PropertyValue> EdgeAccessor::GetProperty(View view, PropertyId property) const {
|
||||
return GetProperty(property, view);
|
||||
}
|
||||
|
||||
Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const {
|
||||
if (!config_.properties_on_edges) return PropertyValue();
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
PropertyValue value;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
deleted = edge_.ptr->deleted;
|
||||
value = edge_.ptr->properties.GetProperty(property);
|
||||
delta = edge_.ptr->delta;
|
||||
}
|
||||
auto exists = true;
|
||||
auto deleted = edge_.ptr->deleted;
|
||||
auto value = edge_.ptr->properties.GetProperty(property);
|
||||
auto *delta = edge_.ptr->delta;
|
||||
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
@ -148,16 +135,11 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view)
|
||||
|
||||
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const {
|
||||
if (!config_.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
std::map<PropertyId, PropertyValue> properties;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
deleted = edge_.ptr->deleted;
|
||||
properties = edge_.ptr->properties.Properties();
|
||||
delta = edge_.ptr->delta;
|
||||
}
|
||||
auto exists = true;
|
||||
auto deleted = edge_.ptr->deleted;
|
||||
auto properties = edge_.ptr->properties.Properties();
|
||||
auto *delta = edge_.ptr->delta;
|
||||
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "storage/v3/edge_ref.hpp"
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
@ -27,33 +28,29 @@ namespace memgraph::storage::v3 {
|
||||
struct Vertex;
|
||||
class VertexAccessor;
|
||||
struct Indices;
|
||||
struct Constraints;
|
||||
|
||||
class EdgeAccessor final {
|
||||
private:
|
||||
friend class Storage;
|
||||
friend class Shard;
|
||||
|
||||
public:
|
||||
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, bool for_deleted = false)
|
||||
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, VertexId from_vertex, VertexId to_vertex, Transaction *transaction,
|
||||
Indices *indices, Config::Items config, bool for_deleted = false)
|
||||
: edge_(edge),
|
||||
edge_type_(edge_type),
|
||||
from_vertex_(from_vertex),
|
||||
to_vertex_(to_vertex),
|
||||
from_vertex_(std::move(from_vertex)),
|
||||
to_vertex_(std::move(to_vertex)),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
schema_validator_{&schema_validator},
|
||||
for_deleted_(for_deleted) {}
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const;
|
||||
|
||||
VertexAccessor FromVertex() const;
|
||||
const VertexId &FromVertex() const;
|
||||
|
||||
VertexAccessor ToVertex() const;
|
||||
const VertexId &ToVertex() const;
|
||||
|
||||
EdgeTypeId EdgeType() const { return edge_type_; }
|
||||
|
||||
@ -68,6 +65,8 @@ class EdgeAccessor final {
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
||||
|
||||
Result<PropertyValue> GetProperty(View view, PropertyId property) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
|
||||
|
||||
@ -88,13 +87,11 @@ class EdgeAccessor final {
|
||||
private:
|
||||
EdgeRef edge_;
|
||||
EdgeTypeId edge_type_;
|
||||
Vertex *from_vertex_;
|
||||
Vertex *to_vertex_;
|
||||
VertexId from_vertex_;
|
||||
VertexId to_vertex_;
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
|
||||
// if the accessor was created for a deleted edge.
|
||||
// Accessor behaves differently for some methods based on this
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
@ -19,30 +21,36 @@
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define STORAGE_DEFINE_ID_TYPE(name) \
|
||||
class name final { \
|
||||
private: \
|
||||
explicit name(uint64_t id) : id_(id) {} \
|
||||
\
|
||||
public: \
|
||||
/* Default constructor to allow serialization or preallocation. */ \
|
||||
name() = default; \
|
||||
\
|
||||
static name FromUint(uint64_t id) { return (name){id}; } \
|
||||
static name FromInt(int64_t id) { return (name){utils::MemcpyCast<uint64_t>(id)}; } \
|
||||
uint64_t AsUint() const { return id_; } \
|
||||
int64_t AsInt() const { return utils::MemcpyCast<int64_t>(id_); } \
|
||||
\
|
||||
private: \
|
||||
uint64_t id_; \
|
||||
}; \
|
||||
static_assert(std::is_trivially_copyable<name>::value, "storage::" #name " must be trivially copyable!"); \
|
||||
inline bool operator==(const name &first, const name &second) { return first.AsUint() == second.AsUint(); } \
|
||||
inline bool operator!=(const name &first, const name &second) { return first.AsUint() != second.AsUint(); } \
|
||||
inline bool operator<(const name &first, const name &second) { return first.AsUint() < second.AsUint(); } \
|
||||
inline bool operator>(const name &first, const name &second) { return first.AsUint() > second.AsUint(); } \
|
||||
inline bool operator<=(const name &first, const name &second) { return first.AsUint() <= second.AsUint(); } \
|
||||
inline bool operator>=(const name &first, const name &second) { return first.AsUint() >= second.AsUint(); }
|
||||
#define STORAGE_DEFINE_ID_TYPE(name) \
|
||||
class name final { \
|
||||
private: \
|
||||
constexpr explicit name(uint64_t id) : id_(id) {} \
|
||||
\
|
||||
public: \
|
||||
/* Default constructor to allow serialization or preallocation. */ \
|
||||
constexpr name() = default; \
|
||||
\
|
||||
constexpr static name FromUint(uint64_t id) { return (name){id}; } \
|
||||
constexpr static name FromInt(int64_t id) { return (name){std::bit_cast<uint64_t>(id)}; } \
|
||||
constexpr uint64_t AsUint() const { return id_; } \
|
||||
constexpr int64_t AsInt() const { return std::bit_cast<int64_t>(id_); } \
|
||||
\
|
||||
private: \
|
||||
uint64_t id_; \
|
||||
}; \
|
||||
static_assert(std::is_trivially_copyable<name>::value, "storage::" #name " must be trivially copyable!"); \
|
||||
constexpr inline bool operator==(const name &first, const name &second) { \
|
||||
return first.AsUint() == second.AsUint(); \
|
||||
} \
|
||||
constexpr inline bool operator!=(const name &first, const name &second) { \
|
||||
return first.AsUint() != second.AsUint(); \
|
||||
} \
|
||||
constexpr inline bool operator<(const name &first, const name &second) { return first.AsUint() < second.AsUint(); } \
|
||||
constexpr inline bool operator>(const name &first, const name &second) { return first.AsUint() > second.AsUint(); } \
|
||||
constexpr inline bool operator<=(const name &first, const name &second) { \
|
||||
return first.AsUint() <= second.AsUint(); \
|
||||
} \
|
||||
constexpr inline bool operator>=(const name &first, const name &second) { return first.AsUint() >= second.AsUint(); }
|
||||
|
||||
STORAGE_DEFINE_ID_TYPE(Gid);
|
||||
STORAGE_DEFINE_ID_TYPE(LabelId);
|
||||
|
@ -13,9 +13,10 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
@ -31,16 +32,16 @@ namespace {
|
||||
template <typename TCallback>
|
||||
bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) {
|
||||
while (delta != nullptr) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
const auto commit_info = *delta->commit_info;
|
||||
// This is a committed change that we see so we shouldn't undo it.
|
||||
if (ts < timestamp) {
|
||||
if (commit_info.is_locally_committed && commit_info.start_or_commit_timestamp.logical_id < timestamp) {
|
||||
break;
|
||||
}
|
||||
if (predicate(*delta)) {
|
||||
return true;
|
||||
}
|
||||
// Move to the next delta.
|
||||
delta = delta->next.load(std::memory_order_acquire);
|
||||
delta = delta->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -52,7 +53,6 @@ bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp)
|
||||
bool deleted{false};
|
||||
const Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
deleted = vertex.deleted;
|
||||
delta = vertex.delta;
|
||||
@ -105,7 +105,6 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId
|
||||
bool deleted{false};
|
||||
const Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value);
|
||||
deleted = vertex.deleted;
|
||||
@ -164,7 +163,6 @@ bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *tr
|
||||
bool has_label{false};
|
||||
const Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
deleted = vertex.deleted;
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
delta = vertex.delta;
|
||||
@ -216,7 +214,6 @@ bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, Propert
|
||||
bool current_value_equal_to_value = value.IsNull();
|
||||
const Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
deleted = vertex.deleted;
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value);
|
||||
@ -269,10 +266,10 @@ void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transacti
|
||||
auto it = index_.find(label);
|
||||
if (it == index_.end()) return;
|
||||
auto acc = it->second.access();
|
||||
acc.insert(Entry{vertex, tx.start_timestamp});
|
||||
acc.insert(Entry{vertex, tx.start_timestamp.logical_id});
|
||||
}
|
||||
|
||||
bool LabelIndex::CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices) {
|
||||
bool LabelIndex::CreateIndex(LabelId label, VerticesSkipList::Accessor vertices) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
auto [it, emplaced] = index_.emplace(std::piecewise_construct, std::forward_as_tuple(label), std::forward_as_tuple());
|
||||
if (!emplaced) {
|
||||
@ -281,7 +278,8 @@ bool LabelIndex::CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor ve
|
||||
}
|
||||
try {
|
||||
auto acc = it->second.access();
|
||||
for (Vertex &vertex : vertices) {
|
||||
for (auto &lgo_vertex : vertices) {
|
||||
auto &vertex = lgo_vertex.vertex;
|
||||
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
|
||||
continue;
|
||||
}
|
||||
@ -304,20 +302,20 @@ std::vector<LabelId> LabelIndex::ListIndices() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||
void LabelIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_timestamp) {
|
||||
for (auto &label_storage : index_) {
|
||||
auto vertices_acc = label_storage.second.access();
|
||||
for (auto it = vertices_acc.begin(); it != vertices_acc.end();) {
|
||||
auto next_it = it;
|
||||
++next_it;
|
||||
|
||||
if (it->timestamp >= oldest_active_start_timestamp) {
|
||||
if (it->timestamp >= clean_up_before_timestamp) {
|
||||
it = next_it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) ||
|
||||
!AnyVersionHasLabel(*it->vertex, label_storage.first, oldest_active_start_timestamp)) {
|
||||
!AnyVersionHasLabel(*it->vertex, label_storage.first, clean_up_before_timestamp)) {
|
||||
vertices_acc.remove(*it);
|
||||
}
|
||||
|
||||
@ -329,7 +327,7 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||
LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||
: self_(self),
|
||||
index_iterator_(index_iterator),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
|
||||
current_vertex_(nullptr) {
|
||||
AdvanceUntilValid();
|
||||
}
|
||||
@ -347,24 +345,23 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
}
|
||||
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, *self_->schema_validator_};
|
||||
current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, self_->config_,
|
||||
*self_->vertex_validator_};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config, const SchemaValidator &schema_validator)
|
||||
Transaction *transaction, Indices *indices, Config::Items config,
|
||||
const VertexValidator &vertex_validator)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
view_(view),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
schema_validator_(&schema_validator) {}
|
||||
vertex_validator_(&vertex_validator) {}
|
||||
|
||||
void LabelIndex::RunGC() {
|
||||
for (auto &index_entry : index_) {
|
||||
@ -398,7 +395,7 @@ void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const T
|
||||
auto prop_value = vertex->properties.GetProperty(label_prop.second);
|
||||
if (!prop_value.IsNull()) {
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp});
|
||||
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp.logical_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,12 +411,12 @@ void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const Property
|
||||
}
|
||||
if (utils::Contains(vertex->labels, label_prop.first)) {
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{value, vertex, tx.start_timestamp});
|
||||
acc.insert(Entry{value, vertex, tx.start_timestamp.logical_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices) {
|
||||
bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, VerticesSkipList::Accessor vertices) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
auto [it, emplaced] =
|
||||
index_.emplace(std::piecewise_construct, std::forward_as_tuple(label, property), std::forward_as_tuple());
|
||||
@ -429,7 +426,8 @@ bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, utils::
|
||||
}
|
||||
try {
|
||||
auto acc = it->second.access();
|
||||
for (Vertex &vertex : vertices) {
|
||||
for (auto &lgo_vertex : vertices) {
|
||||
auto &vertex = lgo_vertex.vertex;
|
||||
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
|
||||
continue;
|
||||
}
|
||||
@ -456,21 +454,21 @@ std::vector<std::pair<LabelId, PropertyId>> LabelPropertyIndex::ListIndices() co
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||
void LabelPropertyIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_timestamp) {
|
||||
for (auto &[label_property, index] : index_) {
|
||||
auto index_acc = index.access();
|
||||
for (auto it = index_acc.begin(); it != index_acc.end();) {
|
||||
auto next_it = it;
|
||||
++next_it;
|
||||
|
||||
if (it->timestamp >= oldest_active_start_timestamp) {
|
||||
if (it->timestamp >= clean_up_before_timestamp) {
|
||||
it = next_it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((next_it != index_acc.end() && it->vertex == next_it->vertex && it->value == next_it->value) ||
|
||||
!AnyVersionHasLabelProperty(*it->vertex, label_property.first, label_property.second, it->value,
|
||||
oldest_active_start_timestamp)) {
|
||||
clean_up_before_timestamp)) {
|
||||
index_acc.remove(*it);
|
||||
}
|
||||
it = next_it;
|
||||
@ -481,7 +479,7 @@ void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_time
|
||||
LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||
: self_(self),
|
||||
index_iterator_(index_iterator),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
|
||||
current_vertex_(nullptr) {
|
||||
AdvanceUntilValid();
|
||||
}
|
||||
@ -520,8 +518,8 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
|
||||
index_iterator_->value, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, *self_->schema_validator_);
|
||||
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->config_,
|
||||
*self_->vertex_validator_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -543,8 +541,8 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
|
||||
PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config, const SchemaValidator &schema_validator)
|
||||
Transaction *transaction, Indices *indices, Config::Items config,
|
||||
const VertexValidator &vertex_validator)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
property_(property),
|
||||
@ -553,9 +551,8 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
|
||||
view_(view),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
schema_validator_(&schema_validator) {
|
||||
vertex_validator_(&vertex_validator) {
|
||||
// We have to fix the bounds that the user provided to us. If the user
|
||||
// provided only one bound we should make sure that only values of that type
|
||||
// are returned by the iterator. We ensure this by supplying either an
|
||||
@ -699,9 +696,9 @@ void LabelPropertyIndex::RunGC() {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp) {
|
||||
indices->label_index.RemoveObsoleteEntries(oldest_active_start_timestamp);
|
||||
indices->label_property_index.RemoveObsoleteEntries(oldest_active_start_timestamp);
|
||||
void RemoveObsoleteEntries(Indices *indices, const uint64_t clean_up_before_timestamp) {
|
||||
indices->label_index.RemoveObsoleteEntries(clean_up_before_timestamp);
|
||||
indices->label_property_index.RemoveObsoleteEntries(clean_up_before_timestamp);
|
||||
}
|
||||
|
||||
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex, const Transaction &tx) {
|
||||
|
@ -18,9 +18,9 @@
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "storage/v3/vertices_skip_list.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
@ -28,7 +28,6 @@
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct Indices;
|
||||
struct Constraints;
|
||||
|
||||
class LabelIndex {
|
||||
private:
|
||||
@ -53,14 +52,14 @@ class LabelIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator)
|
||||
: indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {}
|
||||
LabelIndex(Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
|
||||
: indices_(indices), config_(config), vertex_validator_{&vertex_validator} {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices);
|
||||
bool CreateIndex(LabelId label, VerticesSkipList::Accessor vertices);
|
||||
|
||||
/// Returns false if there was no index to drop
|
||||
bool DropIndex(LabelId label) { return index_.erase(label) > 0; }
|
||||
@ -69,12 +68,12 @@ class LabelIndex {
|
||||
|
||||
std::vector<LabelId> ListIndices() const;
|
||||
|
||||
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
||||
void RemoveObsoleteEntries(uint64_t clean_up_before_timestamp);
|
||||
|
||||
class Iterable {
|
||||
public:
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator);
|
||||
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@ -105,16 +104,15 @@ class LabelIndex {
|
||||
View view_;
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
};
|
||||
|
||||
/// Returns an self with vertices visible from the given transaction.
|
||||
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
|
||||
auto it = index_.find(label);
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
|
||||
return {it->second.access(), label, view, transaction, indices_, constraints_, config_, *schema_validator_};
|
||||
return {it->second.access(), label, view, transaction, indices_, config_, *vertex_validator_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label) {
|
||||
@ -130,9 +128,8 @@ class LabelIndex {
|
||||
private:
|
||||
std::map<LabelId, utils::SkipList<Entry>> index_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
};
|
||||
|
||||
class LabelPropertyIndex {
|
||||
@ -150,9 +147,8 @@ class LabelPropertyIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator)
|
||||
: indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {}
|
||||
LabelPropertyIndex(Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
|
||||
: indices_(indices), config_(config), vertex_validator_{&vertex_validator} {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||
@ -161,7 +157,7 @@ class LabelPropertyIndex {
|
||||
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices);
|
||||
bool CreateIndex(LabelId label, PropertyId property, VerticesSkipList::Accessor vertices);
|
||||
|
||||
bool DropIndex(LabelId label, PropertyId property) { return index_.erase({label, property}) > 0; }
|
||||
|
||||
@ -169,14 +165,14 @@ class LabelPropertyIndex {
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> ListIndices() const;
|
||||
|
||||
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
||||
void RemoveObsoleteEntries(uint64_t clean_up_before_timestamp);
|
||||
|
||||
class Iterable {
|
||||
public:
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator);
|
||||
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@ -211,19 +207,18 @@ class LabelPropertyIndex {
|
||||
View view_;
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
};
|
||||
|
||||
Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
||||
const SchemaValidator &schema_validator_) {
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
Transaction *transaction) {
|
||||
auto it = index_.find({label, property});
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
|
||||
property.AsUint());
|
||||
return {it->second.access(), label, property, lower_bound, upper_bound, view,
|
||||
transaction, indices_, constraints_, config_, schema_validator_};
|
||||
return {it->second.access(), label, property, lower_bound, upper_bound, view,
|
||||
transaction, indices_, config_, *vertex_validator_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
@ -250,15 +245,13 @@ class LabelPropertyIndex {
|
||||
private:
|
||||
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
};
|
||||
|
||||
struct Indices {
|
||||
Indices(Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator)
|
||||
: label_index(this, constraints, config, schema_validator),
|
||||
label_property_index(this, constraints, config, schema_validator) {}
|
||||
Indices(Config::Items config, const VertexValidator &vertex_validator)
|
||||
: label_index(this, config, vertex_validator), label_property_index(this, config, vertex_validator) {}
|
||||
|
||||
// Disable copy and move because members hold pointer to `this`.
|
||||
Indices(const Indices &) = delete;
|
||||
@ -273,7 +266,7 @@ struct Indices {
|
||||
|
||||
/// This function should be called from garbage collection to clean-up the
|
||||
/// index.
|
||||
void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp);
|
||||
void RemoveObsoleteEntries(Indices *indices, uint64_t clean_up_before_timestamp);
|
||||
|
||||
// Indices are updated whenever an update occurs, instead of only on commit or
|
||||
// advance command. This is necessary because we want indices to support `NEW`
|
||||
|
43
src/storage/v3/key_store.cpp
Normal file
43
src/storage/v3/key_store.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
KeyStore::KeyStore(const PrimaryKey &key_values) {
|
||||
for (auto i = 0; i < key_values.size(); ++i) {
|
||||
MG_ASSERT(!key_values[i].IsNull());
|
||||
store_.SetProperty(PropertyId::FromInt(i), key_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyValue KeyStore::GetKey(const size_t index) const { return store_.GetProperty(PropertyId::FromUint(index)); }
|
||||
|
||||
PropertyValue KeyStore::GetKey(const PropertyId property_id) const { return store_.GetProperty(property_id); }
|
||||
|
||||
PrimaryKey KeyStore::Keys() const {
|
||||
auto keys_map = store_.Properties();
|
||||
PrimaryKey keys;
|
||||
keys.reserve(keys_map.size());
|
||||
std::ranges::transform(
|
||||
keys_map, std::back_inserter(keys),
|
||||
[](std::pair<const PropertyId, PropertyValue> &id_and_value) { return std::move(id_and_value.second); });
|
||||
return keys;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
64
src/storage/v3/key_store.hpp
Normal file
64
src/storage/v3/key_store.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <compare>
|
||||
#include <functional>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// Primary key is a collection of primary properties.
|
||||
using PrimaryKey = std::vector<PropertyValue>;
|
||||
|
||||
class KeyStore {
|
||||
public:
|
||||
explicit KeyStore(const PrimaryKey &key_values);
|
||||
|
||||
KeyStore(const KeyStore &) = delete;
|
||||
KeyStore(KeyStore &&other) noexcept = default;
|
||||
KeyStore &operator=(const KeyStore &) = delete;
|
||||
KeyStore &operator=(KeyStore &&other) noexcept = default;
|
||||
|
||||
~KeyStore() = default;
|
||||
|
||||
PropertyValue GetKey(size_t index) const;
|
||||
|
||||
PropertyValue GetKey(PropertyId property) const;
|
||||
|
||||
PrimaryKey Keys() const;
|
||||
|
||||
friend bool operator<(const KeyStore &lhs, const KeyStore &rhs) {
|
||||
// TODO(antaljanosbenjamin): also compare the schema
|
||||
return std::ranges::lexicographical_compare(lhs.Keys(), rhs.Keys(), std::less<PropertyValue>{});
|
||||
}
|
||||
|
||||
friend bool operator==(const KeyStore &lhs, const KeyStore &rhs) {
|
||||
return std::ranges::equal(lhs.Keys(), rhs.Keys());
|
||||
}
|
||||
|
||||
friend bool operator<(const KeyStore &lhs, const PrimaryKey &rhs) {
|
||||
// TODO(antaljanosbenjamin): also compare the schema
|
||||
return std::ranges::lexicographical_compare(lhs.Keys(), rhs, std::less<PropertyValue>{});
|
||||
}
|
||||
|
||||
friend bool operator==(const KeyStore &lhs, const PrimaryKey &rhs) { return std::ranges::equal(lhs.Keys(), rhs); }
|
||||
|
||||
private:
|
||||
PropertyStore store_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
14
src/storage/v3/lexicographically_ordered_vertex.cpp
Normal file
14
src/storage/v3/lexicographically_ordered_vertex.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {} // namespace memgraph::storage::v3
|
42
src/storage/v3/lexicographically_ordered_vertex.hpp
Normal file
42
src/storage/v3/lexicographically_ordered_vertex.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct LexicographicallyOrderedVertex {
|
||||
Vertex vertex;
|
||||
|
||||
friend bool operator==(const LexicographicallyOrderedVertex &lhs, const LexicographicallyOrderedVertex &rhs) {
|
||||
return lhs.vertex.keys == rhs.vertex.keys;
|
||||
}
|
||||
|
||||
friend bool operator<(const LexicographicallyOrderedVertex &lhs, const LexicographicallyOrderedVertex &rhs) {
|
||||
return lhs.vertex.keys < rhs.vertex.keys;
|
||||
}
|
||||
|
||||
// TODO(antaljanosbenjamin): maybe it worth to overload this for std::array to avoid heap construction of the vector
|
||||
friend bool operator==(const LexicographicallyOrderedVertex &lhs, const std::vector<PropertyValue> &rhs) {
|
||||
return lhs.vertex.keys == rhs;
|
||||
}
|
||||
|
||||
friend bool operator<(const LexicographicallyOrderedVertex &lhs, const std::vector<PropertyValue> &rhs) {
|
||||
return lhs.vertex.keys < rhs;
|
||||
}
|
||||
};
|
||||
} // namespace memgraph::storage::v3
|
@ -28,11 +28,9 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
|
||||
// if the transaction is not committed, then its deltas have transaction_id for the timestamp, otherwise they have
|
||||
// its commit timestamp set.
|
||||
// This allows the transaction to see its changes even though it's committed.
|
||||
const auto commit_timestamp = transaction->commit_timestamp
|
||||
? transaction->commit_timestamp->load(std::memory_order_acquire)
|
||||
: transaction->transaction_id;
|
||||
const auto &commit_info = *transaction->commit_info;
|
||||
while (delta != nullptr) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
const auto &delta_commit_info = *delta->commit_info;
|
||||
auto cid = delta->command_id;
|
||||
|
||||
// For SNAPSHOT ISOLATION -> we can only see the changes which were committed before the start of the current
|
||||
@ -44,21 +42,24 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
|
||||
// id value, that the change is committed.
|
||||
//
|
||||
// For READ UNCOMMITTED -> we accept any change.
|
||||
if ((transaction->isolation_level == IsolationLevel::SNAPSHOT_ISOLATION && ts < transaction->start_timestamp) ||
|
||||
(transaction->isolation_level == IsolationLevel::READ_COMMITTED && ts < kTransactionInitialId) ||
|
||||
if ((transaction->isolation_level == IsolationLevel::SNAPSHOT_ISOLATION && delta_commit_info.is_locally_committed &&
|
||||
delta_commit_info.start_or_commit_timestamp.logical_id < transaction->start_timestamp.logical_id) ||
|
||||
(transaction->isolation_level == IsolationLevel::READ_COMMITTED && delta_commit_info.is_locally_committed) ||
|
||||
(transaction->isolation_level == IsolationLevel::READ_UNCOMMITTED)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We shouldn't undo our newest changes because the user requested a NEW
|
||||
// view of the database.
|
||||
if (view == View::NEW && ts == commit_timestamp && cid <= transaction->command_id) {
|
||||
if (view == View::NEW && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
|
||||
cid <= transaction->command_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We shouldn't undo our older changes because the user requested a OLD view
|
||||
// of the database.
|
||||
if (view == View::OLD && ts == commit_timestamp && cid < transaction->command_id) {
|
||||
if (view == View::OLD && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
|
||||
delta->command_id < transaction->command_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -66,7 +67,7 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
|
||||
callback(*delta);
|
||||
|
||||
// Move to the next delta.
|
||||
delta = delta->next.load(std::memory_order_acquire);
|
||||
delta = delta->next;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,8 +80,10 @@ 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 (ts == transaction->transaction_id || ts < transaction->start_timestamp) {
|
||||
const auto &delta_commit_info = *object->delta->commit_info;
|
||||
if (delta_commit_info.start_or_commit_timestamp == transaction->commit_info->start_or_commit_timestamp ||
|
||||
(delta_commit_info.is_locally_committed &&
|
||||
delta_commit_info.start_or_commit_timestamp < transaction->start_timestamp)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -94,8 +97,7 @@ inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
|
||||
/// a `DELETE_OBJECT` delta).
|
||||
/// @throw std::bad_alloc
|
||||
inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
|
||||
transaction->EnsureCommitTimestampExists();
|
||||
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(), transaction->commit_timestamp.get(),
|
||||
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(), transaction->commit_info.get(),
|
||||
transaction->command_id);
|
||||
}
|
||||
|
||||
@ -104,8 +106,7 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
|
||||
/// @throw std::bad_alloc
|
||||
template <typename TObj, class... Args>
|
||||
inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&...args) {
|
||||
transaction->EnsureCommitTimestampExists();
|
||||
auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., transaction->commit_timestamp.get(),
|
||||
auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., transaction->commit_info.get(),
|
||||
transaction->command_id);
|
||||
|
||||
// The operations are written in such order so that both `next` and `prev`
|
||||
@ -114,7 +115,10 @@ 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);
|
||||
// TODO(antaljanosbenjamin): clang-tidy detects (in my opinion a false positive) issue in
|
||||
// `Shard::Accessor::CreateEdge`.
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
|
||||
delta->next = object->delta;
|
||||
// 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
|
||||
|
@ -11,9 +11,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
@ -21,78 +23,47 @@
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class NameIdMapper final {
|
||||
private:
|
||||
struct MapNameToId {
|
||||
std::string name;
|
||||
uint64_t id;
|
||||
|
||||
bool operator<(const MapNameToId &other) const { return name < other.name; }
|
||||
bool operator==(const MapNameToId &other) const { return name == other.name; }
|
||||
|
||||
bool operator<(const std::string_view &other) const { return name < other; }
|
||||
bool operator==(const std::string_view &other) const { return name == other; }
|
||||
};
|
||||
|
||||
struct MapIdToName {
|
||||
uint64_t id;
|
||||
std::string name;
|
||||
|
||||
bool operator<(const MapIdToName &other) const { return id < other.id; }
|
||||
bool operator==(const MapIdToName &other) const { return id == other.id; }
|
||||
|
||||
bool operator<(uint64_t other) const { return id < other; }
|
||||
bool operator==(uint64_t other) const { return id == other; }
|
||||
};
|
||||
|
||||
public:
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
uint64_t NameToId(const std::string_view &name) {
|
||||
auto name_to_id_acc = name_to_id_.access();
|
||||
auto found = name_to_id_acc.find(name);
|
||||
uint64_t id{0};
|
||||
if (found == name_to_id_acc.end()) {
|
||||
uint64_t new_id = counter_.fetch_add(1, std::memory_order_acq_rel);
|
||||
// Try to insert the mapping with the `new_id`, but use the id that is in
|
||||
// the object itself. The object that cointains the mapping is designed to
|
||||
// be a map, so that if the inserted name already exists `insert` will
|
||||
// return an iterator to the existing item. This prevents assignment of
|
||||
// two IDs to the same name when the mapping is being inserted
|
||||
// concurrently from two threads. One ID is wasted in that case, though.
|
||||
id = name_to_id_acc.insert({std::string(name), new_id}).first->id;
|
||||
} else {
|
||||
id = found->id;
|
||||
static constexpr uint64_t kUnmappedId{0};
|
||||
NameIdMapper() = default;
|
||||
|
||||
explicit NameIdMapper(std::unordered_map<uint64_t, std::string> id_to_name) : id_to_name_{std::move(id_to_name)} {
|
||||
for (const auto &[id, name] : id_to_name_) {
|
||||
name_to_id_.emplace(name, id);
|
||||
}
|
||||
auto id_to_name_acc = id_to_name_.access();
|
||||
// We have to try to insert the ID to name mapping even if we are not the
|
||||
// one who assigned the ID because we have to make sure that after this
|
||||
// method returns that both mappings exist.
|
||||
if (id_to_name_acc.find(id) == id_to_name_acc.end()) {
|
||||
// We first try to find the `id` in the map to avoid making an unnecessary
|
||||
// temporary memory allocation when the object already exists.
|
||||
id_to_name_acc.insert({id, std::string(name)});
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// NOTE: Currently this function returns a `const std::string &` instead of a
|
||||
// `std::string` to avoid making unnecessary copies of the string.
|
||||
// Usually, this wouldn't be correct because the accessor to the
|
||||
// `utils::SkipList` is destroyed in this function and that removes the
|
||||
// guarantee that the reference to the value contained in the list will be
|
||||
// valid.
|
||||
// Currently, we never delete anything from the `utils::SkipList` so the
|
||||
// references will always be valid. If you change this class to remove unused
|
||||
// names, be sure to change the signature of this function.
|
||||
const std::string &IdToName(uint64_t id) const {
|
||||
auto id_to_name_acc = id_to_name_.access();
|
||||
auto result = id_to_name_acc.find(id);
|
||||
MG_ASSERT(result != id_to_name_acc.end(), "Trying to get a name for an invalid ID!");
|
||||
return result->name;
|
||||
void StoreMapping(std::unordered_map<uint64_t, std::string> id_to_name) {
|
||||
id_to_name_ = std::move(id_to_name);
|
||||
for (const auto &[id, name] : id_to_name_) {
|
||||
name_to_id_.emplace(name, id);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t NameToId(std::string_view name) const {
|
||||
if (auto it = name_to_id_.find(name); it != name_to_id_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return kUnmappedId;
|
||||
}
|
||||
|
||||
const std::string &IdToName(const uint64_t id) const {
|
||||
auto it = id_to_name_.find(id);
|
||||
MG_ASSERT(it != id_to_name_.end(), "Id not know in mapper!");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint64_t> counter_{0};
|
||||
utils::SkipList<MapNameToId> name_to_id_;
|
||||
utils::SkipList<MapIdToName> id_to_name_;
|
||||
// Necessary for comparison with string_view nad string
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0919r1.html
|
||||
// https://www.cppstories.com/2021/heterogeneous-access-cpp20/
|
||||
struct StringHash {
|
||||
using is_transparent = void;
|
||||
[[nodiscard]] size_t operator()(const char *txt) const { return std::hash<std::string_view>{}(txt); }
|
||||
[[nodiscard]] size_t operator()(std::string_view txt) const { return std::hash<std::string_view>{}(txt); }
|
||||
[[nodiscard]] size_t operator()(const std::string &txt) const { return std::hash<std::string>{}(txt); }
|
||||
};
|
||||
std::unordered_map<uint64_t, std::string> id_to_name_;
|
||||
std::unordered_map<std::string, uint64_t, StringHash, std::equal_to<>> name_to_id_;
|
||||
};
|
||||
} // namespace memgraph::storage::v3
|
||||
|
92
src/storage/v3/path.hpp
Normal file
92
src/storage/v3/path.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class Path {
|
||||
public:
|
||||
using allocator_type = utils::Allocator<char>;
|
||||
explicit Path(const VertexAccessor & /*vertex*/) {}
|
||||
|
||||
template <typename... TOthers>
|
||||
explicit Path(const VertexAccessor &vertex, const TOthers &...others) {}
|
||||
|
||||
template <typename... TOthers>
|
||||
Path(std::allocator_arg_t alloc, utils::MemoryResource *memory, const VertexAccessor &vertex,
|
||||
const TOthers &...others) {}
|
||||
|
||||
Path(const Path & /*other*/) {}
|
||||
|
||||
Path(const Path & /*other*/, utils::MemoryResource * /*memory*/) {}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
Path(Path &&other) noexcept : Path(std::move(other), other.GetMemoryResource()) {}
|
||||
|
||||
Path(Path && /*other*/, utils::MemoryResource * /*memory*/) {}
|
||||
|
||||
Path &operator=(const Path &) = default;
|
||||
|
||||
Path &operator=(Path &&) = default;
|
||||
|
||||
~Path() = default;
|
||||
|
||||
void Expand(const VertexAccessor &vertex) {}
|
||||
|
||||
void Expand(const EdgeAccessor &edge) {}
|
||||
|
||||
template <typename TFirst, typename... TOthers>
|
||||
void Expand(const TFirst &first, const TOthers &...others) {}
|
||||
|
||||
auto size() const;
|
||||
|
||||
std::pmr::vector<VertexAccessor> &vertices() {
|
||||
MG_ASSERT(false, "Using vertices on Path from storage!");
|
||||
return vertices_;
|
||||
}
|
||||
|
||||
std::pmr::vector<EdgeAccessor> &edges() {
|
||||
MG_ASSERT(false, "Using edges on Path from storage!");
|
||||
return edges_;
|
||||
}
|
||||
|
||||
const std::pmr::vector<VertexAccessor> &vertices() const {
|
||||
MG_ASSERT(false, "Using vertices on Path from storage!");
|
||||
return vertices_;
|
||||
}
|
||||
|
||||
const std::pmr::vector<EdgeAccessor> &edges() const {
|
||||
MG_ASSERT(false, "Using edges on Path from storage!");
|
||||
return edges_;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
utils::MemoryResource *GetMemoryResource() const {
|
||||
MG_ASSERT(false, "Using GetMemoryResource on Path from storage!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const Path & /*other*/) const {
|
||||
MG_ASSERT(false, "Using operator= on Path from storage!");
|
||||
return false;
|
||||
};
|
||||
|
||||
private:
|
||||
std::pmr::vector<VertexAccessor> vertices_;
|
||||
std::pmr::vector<EdgeAccessor> edges_;
|
||||
};
|
||||
} // namespace memgraph::storage::v3
|
2
src/storage/v3/replication/.gitignore
vendored
2
src/storage/v3/replication/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
# autogenerated files
|
||||
rpc.hpp
|
@ -1,44 +0,0 @@
|
||||
// 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 <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::storage::v3::replication {
|
||||
struct ReplicationClientConfig {
|
||||
std::optional<double> timeout;
|
||||
// The default delay between main checking/pinging replicas is 1s because
|
||||
// that seems like a reasonable timeframe in which main should notice a
|
||||
// replica is down.
|
||||
std::chrono::seconds replica_check_frequency{1};
|
||||
|
||||
struct SSL {
|
||||
std::string key_file;
|
||||
std::string cert_file;
|
||||
};
|
||||
|
||||
std::optional<SSL> ssl;
|
||||
};
|
||||
|
||||
struct ReplicationServerConfig {
|
||||
struct SSL {
|
||||
std::string key_file;
|
||||
std::string cert_file;
|
||||
std::string ca_file;
|
||||
bool verify_peer;
|
||||
};
|
||||
|
||||
std::optional<SSL> ssl;
|
||||
};
|
||||
} // namespace memgraph::storage::v3::replication
|
@ -1,626 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/replication/replication_client.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
#include "storage/v3/durability/durability.hpp"
|
||||
#include "storage/v3/replication/config.hpp"
|
||||
#include "storage/v3/replication/enums.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
namespace {
|
||||
template <typename>
|
||||
[[maybe_unused]] inline constexpr bool always_false_v = false;
|
||||
} // namespace
|
||||
|
||||
////// ReplicationClient //////
|
||||
Storage::ReplicationClient::ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint,
|
||||
const replication::ReplicationMode mode,
|
||||
const replication::ReplicationClientConfig &config)
|
||||
: name_(std::move(name)), storage_(storage), mode_(mode) {
|
||||
if (config.ssl) {
|
||||
rpc_context_.emplace(config.ssl->key_file, config.ssl->cert_file);
|
||||
} else {
|
||||
rpc_context_.emplace();
|
||||
}
|
||||
|
||||
rpc_client_.emplace(endpoint, &*rpc_context_);
|
||||
TryInitializeClientSync();
|
||||
|
||||
if (config.timeout && replica_state_ != replication::ReplicaState::INVALID) {
|
||||
timeout_.emplace(*config.timeout);
|
||||
timeout_dispatcher_.emplace();
|
||||
}
|
||||
|
||||
// Help the user to get the most accurate replica state possible.
|
||||
if (config.replica_check_frequency > std::chrono::seconds(0)) {
|
||||
replica_checker_.Run("Replica Checker", config.replica_check_frequency, [&] { FrequentCheck(); });
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::TryInitializeClientAsync() {
|
||||
thread_pool_.AddTask([this] {
|
||||
rpc_client_->Abort();
|
||||
this->TryInitializeClientSync();
|
||||
});
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::FrequentCheck() {
|
||||
const auto is_success = std::invoke([this]() {
|
||||
try {
|
||||
auto stream{rpc_client_->Stream<replication::FrequentHeartbeatRpc>()};
|
||||
const auto response = stream.AwaitResponse();
|
||||
return response.success;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// States: READY, REPLICATING, RECOVERY, INVALID
|
||||
// If success && ready, replicating, recovery -> stay the same because something good is going on.
|
||||
// If success && INVALID -> [it's possible that replica came back to life] -> TryInitializeClient.
|
||||
// If fail -> [replica is not reachable at all] -> INVALID state.
|
||||
// NOTE: TryInitializeClient might return nothing if there is a branching point.
|
||||
// NOTE: The early return pattern simplified the code, but the behavior should be as explained.
|
||||
if (!is_success) {
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
return;
|
||||
}
|
||||
if (replica_state_.load() == replication::ReplicaState::INVALID) {
|
||||
TryInitializeClientAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// @throws rpc::RpcFailedException
|
||||
void Storage::ReplicationClient::InitializeClient() {
|
||||
uint64_t current_commit_timestamp{kTimestampInitialId};
|
||||
|
||||
std::optional<std::string> epoch_id;
|
||||
{
|
||||
// epoch_id_ can be changed if we don't take this lock
|
||||
std::unique_lock engine_guard(storage_->engine_lock_);
|
||||
epoch_id.emplace(storage_->epoch_id_);
|
||||
}
|
||||
|
||||
auto stream{rpc_client_->Stream<replication::HeartbeatRpc>(storage_->last_commit_timestamp_, std::move(*epoch_id))};
|
||||
|
||||
const auto response = stream.AwaitResponse();
|
||||
std::optional<uint64_t> branching_point;
|
||||
if (response.epoch_id != storage_->epoch_id_ && response.current_commit_timestamp != kTimestampInitialId) {
|
||||
const auto &epoch_history = storage_->epoch_history_;
|
||||
const auto epoch_info_iter =
|
||||
std::find_if(epoch_history.crbegin(), epoch_history.crend(),
|
||||
[&](const auto &epoch_info) { return epoch_info.first == response.epoch_id; });
|
||||
if (epoch_info_iter == epoch_history.crend()) {
|
||||
branching_point = 0;
|
||||
} else if (epoch_info_iter->second != response.current_commit_timestamp) {
|
||||
branching_point = epoch_info_iter->second;
|
||||
}
|
||||
}
|
||||
if (branching_point) {
|
||||
spdlog::error(
|
||||
"Replica {} cannot be used with this instance. Please start a clean "
|
||||
"instance of Memgraph server on the specified endpoint.",
|
||||
name_);
|
||||
return;
|
||||
}
|
||||
|
||||
current_commit_timestamp = response.current_commit_timestamp;
|
||||
spdlog::trace("Current timestamp on replica: {}", current_commit_timestamp);
|
||||
spdlog::trace("Current timestamp on main: {}", storage_->last_commit_timestamp_.load());
|
||||
if (current_commit_timestamp == storage_->last_commit_timestamp_.load()) {
|
||||
spdlog::debug("Replica '{}' up to date", name_);
|
||||
std::unique_lock client_guard{client_lock_};
|
||||
replica_state_.store(replication::ReplicaState::READY);
|
||||
} else {
|
||||
spdlog::debug("Replica '{}' is behind", name_);
|
||||
{
|
||||
std::unique_lock client_guard{client_lock_};
|
||||
replica_state_.store(replication::ReplicaState::RECOVERY);
|
||||
}
|
||||
thread_pool_.AddTask([=, this] { this->RecoverReplica(current_commit_timestamp); });
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::TryInitializeClientSync() {
|
||||
try {
|
||||
InitializeClient();
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
std::unique_lock client_guarde{client_lock_};
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
spdlog::error(utils::MessageWithLink("Failed to connect to replica {} at the endpoint {}.", name_,
|
||||
rpc_client_->Endpoint(), "https://memgr.ph/replication"));
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::HandleRpcFailure() {
|
||||
spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication"));
|
||||
TryInitializeClientAsync();
|
||||
}
|
||||
|
||||
replication::SnapshotRes Storage::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) {
|
||||
auto stream{rpc_client_->Stream<replication::SnapshotRpc>()};
|
||||
replication::Encoder encoder(stream.GetBuilder());
|
||||
encoder.WriteFile(path);
|
||||
return stream.AwaitResponse();
|
||||
}
|
||||
|
||||
replication::WalFilesRes Storage::ReplicationClient::TransferWalFiles(
|
||||
const std::vector<std::filesystem::path> &wal_files) {
|
||||
MG_ASSERT(!wal_files.empty(), "Wal files list is empty!");
|
||||
auto stream{rpc_client_->Stream<replication::WalFilesRpc>(wal_files.size())};
|
||||
replication::Encoder encoder(stream.GetBuilder());
|
||||
for (const auto &wal : wal_files) {
|
||||
spdlog::debug("Sending wal file: {}", wal);
|
||||
encoder.WriteFile(wal);
|
||||
}
|
||||
|
||||
return stream.AwaitResponse();
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) {
|
||||
std::unique_lock guard(client_lock_);
|
||||
const auto status = replica_state_.load();
|
||||
switch (status) {
|
||||
case replication::ReplicaState::RECOVERY:
|
||||
spdlog::debug("Replica {} is behind MAIN instance", name_);
|
||||
return;
|
||||
case replication::ReplicaState::REPLICATING:
|
||||
spdlog::debug("Replica {} missed a transaction", name_);
|
||||
// We missed a transaction because we're still replicating
|
||||
// the previous transaction so we need to go to RECOVERY
|
||||
// state to catch up with the missing transaction
|
||||
// We cannot queue the recovery process here because
|
||||
// an error can happen while we're replicating the previous
|
||||
// transaction after which the client should go to
|
||||
// INVALID state before starting the recovery process
|
||||
replica_state_.store(replication::ReplicaState::RECOVERY);
|
||||
return;
|
||||
case replication::ReplicaState::INVALID:
|
||||
HandleRpcFailure();
|
||||
return;
|
||||
case replication::ReplicaState::READY:
|
||||
MG_ASSERT(!replica_stream_);
|
||||
try {
|
||||
replica_stream_.emplace(ReplicaStream{this, storage_->last_commit_timestamp_.load(), current_wal_seq_num});
|
||||
replica_state_.store(replication::ReplicaState::REPLICATING);
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
HandleRpcFailure();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback) {
|
||||
// We can only check the state because it guarantees to be only
|
||||
// valid during a single transaction replication (if the assumption
|
||||
// that this and other transaction replication functions can only be
|
||||
// called from a one thread stands)
|
||||
if (replica_state_ != replication::ReplicaState::REPLICATING) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
callback(*replica_stream_);
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
{
|
||||
std::unique_lock client_guard{client_lock_};
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
}
|
||||
HandleRpcFailure();
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::FinalizeTransactionReplication() {
|
||||
// We can only check the state because it guarantees to be only
|
||||
// valid during a single transaction replication (if the assumption
|
||||
// that this and other transaction replication functions can only be
|
||||
// called from a one thread stands)
|
||||
if (replica_state_ != replication::ReplicaState::REPLICATING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode_ == replication::ReplicationMode::ASYNC) {
|
||||
thread_pool_.AddTask([this] { this->FinalizeTransactionReplicationInternal(); });
|
||||
} else if (timeout_) {
|
||||
MG_ASSERT(mode_ == replication::ReplicationMode::SYNC, "Only SYNC replica can have a timeout.");
|
||||
MG_ASSERT(timeout_dispatcher_, "Timeout thread is missing");
|
||||
timeout_dispatcher_->WaitForTaskToFinish();
|
||||
|
||||
timeout_dispatcher_->active = true;
|
||||
thread_pool_.AddTask([&, this] {
|
||||
this->FinalizeTransactionReplicationInternal();
|
||||
std::unique_lock main_guard(timeout_dispatcher_->main_lock);
|
||||
// TimerThread can finish waiting for timeout
|
||||
timeout_dispatcher_->active = false;
|
||||
// Notify the main thread
|
||||
timeout_dispatcher_->main_cv.notify_one();
|
||||
});
|
||||
|
||||
timeout_dispatcher_->StartTimeoutTask(*timeout_);
|
||||
|
||||
// Wait until one of the threads notifies us that they finished executing
|
||||
// Both threads should first set the active flag to false
|
||||
{
|
||||
std::unique_lock main_guard(timeout_dispatcher_->main_lock);
|
||||
timeout_dispatcher_->main_cv.wait(main_guard, [&] { return !timeout_dispatcher_->active.load(); });
|
||||
}
|
||||
|
||||
// TODO (antonio2368): Document and/or polish SEMI-SYNC to ASYNC fallback.
|
||||
if (replica_state_ == replication::ReplicaState::REPLICATING) {
|
||||
mode_ = replication::ReplicationMode::ASYNC;
|
||||
timeout_.reset();
|
||||
// This can only happen if we timeouted so we are sure that
|
||||
// Timeout task finished
|
||||
// We need to delete timeout dispatcher AFTER the replication
|
||||
// finished because it tries to acquire the timeout lock
|
||||
// and acces the `active` variable`
|
||||
thread_pool_.AddTask([this] { timeout_dispatcher_.reset(); });
|
||||
}
|
||||
} else {
|
||||
FinalizeTransactionReplicationInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() {
|
||||
MG_ASSERT(replica_stream_, "Missing stream for transaction deltas");
|
||||
try {
|
||||
auto response = replica_stream_->Finalize();
|
||||
replica_stream_.reset();
|
||||
std::unique_lock client_guard(client_lock_);
|
||||
if (!response.success || replica_state_ == replication::ReplicaState::RECOVERY) {
|
||||
replica_state_.store(replication::ReplicaState::RECOVERY);
|
||||
thread_pool_.AddTask([&, this] { this->RecoverReplica(response.current_commit_timestamp); });
|
||||
} else {
|
||||
replica_state_.store(replication::ReplicaState::READY);
|
||||
}
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
replica_stream_.reset();
|
||||
{
|
||||
std::unique_lock client_guard(client_lock_);
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
}
|
||||
HandleRpcFailure();
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) {
|
||||
while (true) {
|
||||
auto file_locker = storage_->file_retainer_.AddLocker();
|
||||
|
||||
const auto steps = GetRecoverySteps(replica_commit, &file_locker);
|
||||
for (const auto &recovery_step : steps) {
|
||||
try {
|
||||
std::visit(
|
||||
[&, this]<typename T>(T &&arg) {
|
||||
using StepType = std::remove_cvref_t<T>;
|
||||
if constexpr (std::is_same_v<StepType, RecoverySnapshot>) {
|
||||
spdlog::debug("Sending the latest snapshot file: {}", arg);
|
||||
auto response = TransferSnapshot(arg);
|
||||
replica_commit = response.current_commit_timestamp;
|
||||
} else if constexpr (std::is_same_v<StepType, RecoveryWals>) {
|
||||
spdlog::debug("Sending the latest wal files");
|
||||
auto response = TransferWalFiles(arg);
|
||||
replica_commit = response.current_commit_timestamp;
|
||||
} else if constexpr (std::is_same_v<StepType, RecoveryCurrentWal>) {
|
||||
std::unique_lock transaction_guard(storage_->engine_lock_);
|
||||
if (storage_->wal_file_ && storage_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) {
|
||||
storage_->wal_file_->DisableFlushing();
|
||||
transaction_guard.unlock();
|
||||
spdlog::debug("Sending current wal file");
|
||||
replica_commit = ReplicateCurrentWal();
|
||||
storage_->wal_file_->EnableFlushing();
|
||||
}
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "Missing type from variant visitor");
|
||||
}
|
||||
},
|
||||
recovery_step);
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
{
|
||||
std::unique_lock client_guard{client_lock_};
|
||||
replica_state_.store(replication::ReplicaState::INVALID);
|
||||
}
|
||||
HandleRpcFailure();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::trace("Current timestamp on replica: {}", replica_commit);
|
||||
// To avoid the situation where we read a correct commit timestamp in
|
||||
// one thread, and after that another thread commits a different a
|
||||
// transaction and THEN we set the state to READY in the first thread,
|
||||
// we set this lock before checking the timestamp.
|
||||
// We will detect that the state is invalid during the next commit,
|
||||
// because replication::AppendDeltasRpc sends the last commit timestamp which
|
||||
// replica checks if it's the same last commit timestamp it received
|
||||
// and we will go to recovery.
|
||||
// By adding this lock, we can avoid that, and go to RECOVERY immediately.
|
||||
std::unique_lock client_guard{client_lock_};
|
||||
SPDLOG_INFO("Replica timestamp: {}", replica_commit);
|
||||
SPDLOG_INFO("Last commit: {}", storage_->last_commit_timestamp_);
|
||||
if (storage_->last_commit_timestamp_.load() == replica_commit) {
|
||||
replica_state_.store(replication::ReplicaState::READY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Storage::ReplicationClient::ReplicateCurrentWal() {
|
||||
const auto &wal_file = storage_->wal_file_;
|
||||
auto stream = TransferCurrentWalFile();
|
||||
stream.AppendFilename(wal_file->Path().filename());
|
||||
utils::InputFile file;
|
||||
MG_ASSERT(file.Open(storage_->wal_file_->Path()), "Failed to open current WAL file!");
|
||||
const auto [buffer, buffer_size] = wal_file->CurrentFileBuffer();
|
||||
stream.AppendSize(file.GetSize() + buffer_size);
|
||||
stream.AppendFileData(&file);
|
||||
stream.AppendBufferData(buffer, buffer_size);
|
||||
auto response = stream.Finalize();
|
||||
return response.current_commit_timestamp;
|
||||
}
|
||||
|
||||
/// This method tries to find the optimal path for recoverying a single replica.
|
||||
/// Based on the last commit transfered to replica it tries to update the
|
||||
/// replica using durability files - WALs and Snapshots. WAL files are much
|
||||
/// smaller in size as they contain only the Deltas (changes) made during the
|
||||
/// transactions while Snapshots contain all the data. For that reason we prefer
|
||||
/// WALs as much as possible. As the WAL file that is currently being updated
|
||||
/// can change during the process we ignore it as much as possible. Also, it
|
||||
/// uses the transaction lock so lokcing it can be really expensive. After we
|
||||
/// fetch the list of finalized WALs, we try to find the longest chain of
|
||||
/// sequential WALs, starting from the latest one, that will update the recovery
|
||||
/// with the all missed updates. If the WAL chain cannot be created, replica is
|
||||
/// behind by a lot, so we use the regular recovery process, we send the latest
|
||||
/// snapshot and all the necessary WAL files, starting from the newest WAL that
|
||||
/// contains a timestamp before the snapshot. If we registered the existence of
|
||||
/// the current WAL, we add the sequence number we read from it to the recovery
|
||||
/// process. After all the other steps are finished, if the current WAL contains
|
||||
/// the same sequence number, it's the same WAL we read while fetching the
|
||||
/// recovery steps, so we can safely send it to the replica.
|
||||
/// We assume that the property of preserving at least 1 WAL before the snapshot
|
||||
/// is satisfied as we extract the timestamp information from it.
|
||||
std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient::GetRecoverySteps(
|
||||
const uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker) {
|
||||
// First check if we can recover using the current wal file only
|
||||
// otherwise save the seq_num of the current wal file
|
||||
// This lock is also necessary to force the missed transaction to finish.
|
||||
std::optional<uint64_t> current_wal_seq_num;
|
||||
std::optional<uint64_t> current_wal_from_timestamp;
|
||||
if (std::unique_lock transtacion_guard(storage_->engine_lock_); storage_->wal_file_) {
|
||||
current_wal_seq_num.emplace(storage_->wal_file_->SequenceNumber());
|
||||
current_wal_from_timestamp.emplace(storage_->wal_file_->FromTimestamp());
|
||||
}
|
||||
|
||||
auto locker_acc = file_locker->Access();
|
||||
auto wal_files = durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_, current_wal_seq_num);
|
||||
MG_ASSERT(wal_files, "Wal files could not be loaded");
|
||||
|
||||
auto snapshot_files = durability::GetSnapshotFiles(storage_->snapshot_directory_, storage_->uuid_);
|
||||
std::optional<durability::SnapshotDurabilityInfo> latest_snapshot;
|
||||
if (!snapshot_files.empty()) {
|
||||
std::sort(snapshot_files.begin(), snapshot_files.end());
|
||||
latest_snapshot.emplace(std::move(snapshot_files.back()));
|
||||
}
|
||||
|
||||
std::vector<RecoveryStep> recovery_steps;
|
||||
|
||||
// No finalized WAL files were found. This means the difference is contained
|
||||
// inside the current WAL or the snapshot.
|
||||
if (wal_files->empty()) {
|
||||
if (current_wal_from_timestamp && replica_commit >= *current_wal_from_timestamp) {
|
||||
MG_ASSERT(current_wal_seq_num);
|
||||
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
|
||||
return recovery_steps;
|
||||
}
|
||||
|
||||
// Without the finalized WAL containing the current timestamp of replica,
|
||||
// we cannot know if the difference is only in the current WAL or we need
|
||||
// to send the snapshot.
|
||||
if (latest_snapshot) {
|
||||
locker_acc.AddPath(latest_snapshot->path);
|
||||
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
|
||||
}
|
||||
// if there are no finalized WAL files, snapshot left the current WAL
|
||||
// as the WAL file containing a transaction before snapshot creation
|
||||
// so we can be sure that the current WAL is present
|
||||
MG_ASSERT(current_wal_seq_num);
|
||||
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
|
||||
return recovery_steps;
|
||||
}
|
||||
|
||||
// Find the longest chain of WALs for recovery.
|
||||
// The chain consists ONLY of sequential WALs.
|
||||
auto rwal_it = wal_files->rbegin();
|
||||
|
||||
// if the last finalized WAL is before the replica commit
|
||||
// then we can recovery only from current WAL
|
||||
if (rwal_it->to_timestamp <= replica_commit) {
|
||||
MG_ASSERT(current_wal_seq_num);
|
||||
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
|
||||
return recovery_steps;
|
||||
}
|
||||
|
||||
uint64_t previous_seq_num{rwal_it->seq_num};
|
||||
for (; rwal_it != wal_files->rend(); ++rwal_it) {
|
||||
// If the difference between two consecutive wal files is not 0 or 1
|
||||
// we have a missing WAL in our chain
|
||||
if (previous_seq_num - rwal_it->seq_num > 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find first WAL that contains up to replica commit, i.e. WAL
|
||||
// that is before the replica commit or conatins the replica commit
|
||||
// as the last committed transaction OR we managed to find the first WAL
|
||||
// file.
|
||||
if (replica_commit >= rwal_it->from_timestamp || rwal_it->seq_num == 0) {
|
||||
if (replica_commit >= rwal_it->to_timestamp) {
|
||||
// We want the WAL after because the replica already contains all the
|
||||
// commits from this WAL
|
||||
--rwal_it;
|
||||
}
|
||||
std::vector<std::filesystem::path> wal_chain;
|
||||
auto distance_from_first = std::distance(rwal_it, wal_files->rend() - 1);
|
||||
// We have managed to create WAL chain
|
||||
// We need to lock these files and add them to the chain
|
||||
for (auto result_wal_it = wal_files->begin() + distance_from_first; result_wal_it != wal_files->end();
|
||||
++result_wal_it) {
|
||||
locker_acc.AddPath(result_wal_it->path);
|
||||
wal_chain.push_back(std::move(result_wal_it->path));
|
||||
}
|
||||
|
||||
recovery_steps.emplace_back(std::in_place_type_t<RecoveryWals>{}, std::move(wal_chain));
|
||||
|
||||
if (current_wal_seq_num) {
|
||||
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
|
||||
}
|
||||
return recovery_steps;
|
||||
}
|
||||
|
||||
previous_seq_num = rwal_it->seq_num;
|
||||
}
|
||||
|
||||
MG_ASSERT(latest_snapshot, "Invalid durability state, missing snapshot");
|
||||
// We didn't manage to find a WAL chain, we need to send the latest snapshot
|
||||
// with its WALs
|
||||
locker_acc.AddPath(latest_snapshot->path);
|
||||
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
|
||||
|
||||
std::vector<std::filesystem::path> recovery_wal_files;
|
||||
auto wal_it = wal_files->begin();
|
||||
for (; wal_it != wal_files->end(); ++wal_it) {
|
||||
// Assuming recovery process is correct the snashpot should
|
||||
// always retain a single WAL that contains a transaction
|
||||
// before its creation
|
||||
if (latest_snapshot->start_timestamp < wal_it->to_timestamp) {
|
||||
if (latest_snapshot->start_timestamp < wal_it->from_timestamp) {
|
||||
MG_ASSERT(wal_it != wal_files->begin(), "Invalid durability files state");
|
||||
--wal_it;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; wal_it != wal_files->end(); ++wal_it) {
|
||||
locker_acc.AddPath(wal_it->path);
|
||||
recovery_wal_files.push_back(std::move(wal_it->path));
|
||||
}
|
||||
|
||||
// We only have a WAL before the snapshot
|
||||
if (recovery_wal_files.empty()) {
|
||||
locker_acc.AddPath(wal_files->back().path);
|
||||
recovery_wal_files.push_back(std::move(wal_files->back().path));
|
||||
}
|
||||
|
||||
recovery_steps.emplace_back(std::in_place_type_t<RecoveryWals>{}, std::move(recovery_wal_files));
|
||||
|
||||
if (current_wal_seq_num) {
|
||||
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
|
||||
}
|
||||
|
||||
return recovery_steps;
|
||||
}
|
||||
|
||||
////// TimeoutDispatcher //////
|
||||
void Storage::ReplicationClient::TimeoutDispatcher::WaitForTaskToFinish() {
|
||||
// Wait for the previous timeout task to finish
|
||||
std::unique_lock main_guard(main_lock);
|
||||
main_cv.wait(main_guard, [&] { return finished; });
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::TimeoutDispatcher::StartTimeoutTask(const double timeout) {
|
||||
timeout_pool.AddTask([timeout, this] {
|
||||
finished = false;
|
||||
using std::chrono::steady_clock;
|
||||
const auto timeout_duration =
|
||||
std::chrono::duration_cast<steady_clock::duration>(std::chrono::duration<double>(timeout));
|
||||
const auto end_time = steady_clock::now() + timeout_duration;
|
||||
while (active && (steady_clock::now() < end_time)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
std::unique_lock main_guard(main_lock);
|
||||
finished = true;
|
||||
active = false;
|
||||
main_cv.notify_one();
|
||||
});
|
||||
}
|
||||
////// ReplicaStream //////
|
||||
Storage::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self,
|
||||
const uint64_t previous_commit_timestamp,
|
||||
const uint64_t current_seq_num)
|
||||
: self_(self),
|
||||
stream_(self_->rpc_client_->Stream<replication::AppendDeltasRpc>(previous_commit_timestamp, current_seq_num)) {
|
||||
replication::Encoder encoder{stream_.GetBuilder()};
|
||||
encoder.WriteString(self_->storage_->epoch_id_);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex,
|
||||
uint64_t final_commit_timestamp) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, self_->storage_->config_.items, delta, vertex,
|
||||
final_commit_timestamp);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge,
|
||||
uint64_t final_commit_timestamp) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, delta, edge, final_commit_timestamp);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
EncodeTransactionEnd(&encoder, final_commit_timestamp);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation,
|
||||
LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t timestamp) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, label, properties, timestamp);
|
||||
}
|
||||
|
||||
replication::AppendDeltasRes Storage::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); }
|
||||
|
||||
////// CurrentWalHandler //////
|
||||
Storage::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self)
|
||||
: self_(self), stream_(self_->rpc_client_->Stream<replication::CurrentWalRpc>()) {}
|
||||
|
||||
void Storage::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
encoder.WriteString(filename);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
encoder.WriteUint(size);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
encoder.WriteFileData(file);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
encoder.WriteBuffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
replication::CurrentWalRes Storage::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); }
|
||||
} // namespace memgraph::storage::v3
|
@ -1,203 +0,0 @@
|
||||
// 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 <chrono>
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
#include "rpc/client.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/delta.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/replication/config.hpp"
|
||||
#include "storage/v3/replication/enums.hpp"
|
||||
#include "storage/v3/replication/rpc.hpp"
|
||||
#include "storage/v3/replication/serialization.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class Storage::ReplicationClient {
|
||||
public:
|
||||
ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint,
|
||||
replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {});
|
||||
|
||||
// Handler used for transfering the current transaction.
|
||||
class ReplicaStream {
|
||||
private:
|
||||
friend class ReplicationClient;
|
||||
explicit ReplicaStream(ReplicationClient *self, uint64_t previous_commit_timestamp, uint64_t current_seq_num);
|
||||
|
||||
public:
|
||||
/// @throw rpc::RpcFailedException
|
||||
void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp);
|
||||
|
||||
/// @throw rpc::RpcFailedException
|
||||
void AppendDelta(const Delta &delta, const Edge &edge, uint64_t final_commit_timestamp);
|
||||
|
||||
/// @throw rpc::RpcFailedException
|
||||
void AppendTransactionEnd(uint64_t final_commit_timestamp);
|
||||
|
||||
/// @throw rpc::RpcFailedException
|
||||
void AppendOperation(durability::StorageGlobalOperation operation, LabelId label,
|
||||
const std::set<PropertyId> &properties, uint64_t timestamp);
|
||||
|
||||
private:
|
||||
/// @throw rpc::RpcFailedException
|
||||
replication::AppendDeltasRes Finalize();
|
||||
|
||||
ReplicationClient *self_;
|
||||
rpc::Client::StreamHandler<replication::AppendDeltasRpc> stream_;
|
||||
};
|
||||
|
||||
// Handler for transfering the current WAL file whose data is
|
||||
// contained in the internal buffer and the file.
|
||||
class CurrentWalHandler {
|
||||
private:
|
||||
friend class ReplicationClient;
|
||||
explicit CurrentWalHandler(ReplicationClient *self);
|
||||
|
||||
public:
|
||||
void AppendFilename(const std::string &filename);
|
||||
|
||||
void AppendSize(size_t size);
|
||||
|
||||
void AppendFileData(utils::InputFile *file);
|
||||
|
||||
void AppendBufferData(const uint8_t *buffer, size_t buffer_size);
|
||||
|
||||
/// @throw rpc::RpcFailedException
|
||||
replication::CurrentWalRes Finalize();
|
||||
|
||||
private:
|
||||
ReplicationClient *self_;
|
||||
rpc::Client::StreamHandler<replication::CurrentWalRpc> stream_;
|
||||
};
|
||||
|
||||
void StartTransactionReplication(uint64_t current_wal_seq_num);
|
||||
|
||||
// Replication clients can be removed at any point
|
||||
// so to avoid any complexity of checking if the client was removed whenever
|
||||
// we want to send part of transaction and to avoid adding some GC logic this
|
||||
// function will run a callback if, after previously callling
|
||||
// StartTransactionReplication, stream is created.
|
||||
void IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback);
|
||||
|
||||
void FinalizeTransactionReplication();
|
||||
|
||||
// Transfer the snapshot file.
|
||||
// @param path Path of the snapshot file.
|
||||
replication::SnapshotRes TransferSnapshot(const std::filesystem::path &path);
|
||||
|
||||
CurrentWalHandler TransferCurrentWalFile() { return CurrentWalHandler{this}; }
|
||||
|
||||
// Transfer the WAL files
|
||||
replication::WalFilesRes TransferWalFiles(const std::vector<std::filesystem::path> &wal_files);
|
||||
|
||||
const auto &Name() const { return name_; }
|
||||
|
||||
auto State() const { return replica_state_.load(); }
|
||||
|
||||
auto Mode() const { return mode_; }
|
||||
|
||||
auto Timeout() const { return timeout_; }
|
||||
|
||||
const auto &Endpoint() const { return rpc_client_->Endpoint(); }
|
||||
|
||||
private:
|
||||
void FinalizeTransactionReplicationInternal();
|
||||
|
||||
void RecoverReplica(uint64_t replica_commit);
|
||||
|
||||
uint64_t ReplicateCurrentWal();
|
||||
|
||||
using RecoveryWals = std::vector<std::filesystem::path>;
|
||||
struct RecoveryCurrentWal {
|
||||
uint64_t current_wal_seq_num;
|
||||
|
||||
explicit RecoveryCurrentWal(const uint64_t current_wal_seq_num) : current_wal_seq_num(current_wal_seq_num) {}
|
||||
};
|
||||
using RecoverySnapshot = std::filesystem::path;
|
||||
using RecoveryStep = std::variant<RecoverySnapshot, RecoveryWals, RecoveryCurrentWal>;
|
||||
|
||||
std::vector<RecoveryStep> GetRecoverySteps(uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker);
|
||||
|
||||
void FrequentCheck();
|
||||
void InitializeClient();
|
||||
void TryInitializeClientSync();
|
||||
void TryInitializeClientAsync();
|
||||
void HandleRpcFailure();
|
||||
|
||||
std::string name_;
|
||||
Storage *storage_;
|
||||
std::optional<communication::ClientContext> rpc_context_;
|
||||
std::optional<rpc::Client> rpc_client_;
|
||||
|
||||
std::optional<ReplicaStream> replica_stream_;
|
||||
replication::ReplicationMode mode_{replication::ReplicationMode::SYNC};
|
||||
|
||||
// Dispatcher class for timeout tasks
|
||||
struct TimeoutDispatcher {
|
||||
explicit TimeoutDispatcher(){};
|
||||
|
||||
void WaitForTaskToFinish();
|
||||
|
||||
void StartTimeoutTask(double timeout);
|
||||
|
||||
// If the Timeout task should continue waiting
|
||||
std::atomic<bool> active{false};
|
||||
|
||||
std::mutex main_lock;
|
||||
std::condition_variable main_cv;
|
||||
|
||||
private:
|
||||
// if the Timeout task finished executing
|
||||
bool finished{true};
|
||||
|
||||
utils::ThreadPool timeout_pool{1};
|
||||
};
|
||||
|
||||
std::optional<double> timeout_;
|
||||
std::optional<TimeoutDispatcher> timeout_dispatcher_;
|
||||
|
||||
utils::SpinLock client_lock_;
|
||||
// This thread pool is used for background tasks so we don't
|
||||
// block the main storage thread
|
||||
// We use only 1 thread for 2 reasons:
|
||||
// - background tasks ALWAYS contain some kind of RPC communication.
|
||||
// We can't have multiple RPC communication from a same client
|
||||
// because that's not logically valid (e.g. you cannot send a snapshot
|
||||
// and WAL at a same time because WAL will arrive earlier and be applied
|
||||
// before the snapshot which is not correct)
|
||||
// - the implementation is simplified as we have a total control of what
|
||||
// this pool is executing. Also, we can simply queue multiple tasks
|
||||
// and be sure of the execution order.
|
||||
// Not having mulitple possible threads in the same client allows us
|
||||
// to ignore concurrency problems inside the client.
|
||||
utils::ThreadPool thread_pool_{1};
|
||||
std::atomic<replication::ReplicaState> replica_state_{replication::ReplicaState::INVALID};
|
||||
|
||||
utils::Scheduler replica_checker_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -1,576 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/replication/replication_server.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
#include "storage/v3/durability/durability.hpp"
|
||||
#include "storage/v3/durability/paths.hpp"
|
||||
#include "storage/v3/durability/serialization.hpp"
|
||||
#include "storage/v3/durability/snapshot.hpp"
|
||||
#include "storage/v3/durability/version.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/replication/config.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
namespace {
|
||||
std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder *decoder) {
|
||||
try {
|
||||
auto timestamp = ReadWalDeltaHeader(decoder);
|
||||
SPDLOG_INFO(" Timestamp {}", timestamp);
|
||||
auto delta = ReadWalDeltaData(decoder);
|
||||
return {timestamp, delta};
|
||||
} catch (const slk::SlkReaderException &) {
|
||||
throw utils::BasicException("Missing data!");
|
||||
} catch (const durability::RecoveryFailure &) {
|
||||
throw utils::BasicException("Invalid data!");
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::Endpoint endpoint,
|
||||
const replication::ReplicationServerConfig &config)
|
||||
: storage_(storage) {
|
||||
// Create RPC server.
|
||||
if (config.ssl) {
|
||||
rpc_server_context_.emplace(config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
|
||||
config.ssl->verify_peer);
|
||||
} else {
|
||||
rpc_server_context_.emplace();
|
||||
}
|
||||
// NOTE: The replication server must have a single thread for processing
|
||||
// because there is no need for more processing threads - each replica can
|
||||
// have only a single main server. Also, the single-threaded guarantee
|
||||
// simplifies the rest of the implementation.
|
||||
rpc_server_.emplace(std::move(endpoint), &*rpc_server_context_,
|
||||
/* workers_count = */ 1);
|
||||
|
||||
rpc_server_->Register<replication::HeartbeatRpc>([this](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received HeartbeatRpc");
|
||||
this->HeartbeatHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Register<replication::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received FrequentHeartbeatRpc");
|
||||
FrequentHeartbeatHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Register<replication::AppendDeltasRpc>([this](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received AppendDeltasRpc");
|
||||
this->AppendDeltasHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Register<replication::SnapshotRpc>([this](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SnapshotRpc");
|
||||
this->SnapshotHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Register<replication::WalFilesRpc>([this](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received WalFilesRpc");
|
||||
this->WalFilesHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Register<replication::CurrentWalRpc>([this](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received CurrentWalRpc");
|
||||
this->CurrentWalHandler(req_reader, res_builder);
|
||||
});
|
||||
rpc_server_->Start();
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::HeartbeatReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
replication::HeartbeatRes res{true, storage_->last_commit_timestamp_.load(), storage_->epoch_id_};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::FrequentHeartbeatReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
replication::FrequentHeartbeatRes res{true};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::AppendDeltasReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
replication::Decoder decoder(req_reader);
|
||||
|
||||
auto maybe_epoch_id = decoder.ReadString();
|
||||
MG_ASSERT(maybe_epoch_id, "Invalid replication message");
|
||||
|
||||
if (*maybe_epoch_id != storage_->epoch_id_) {
|
||||
storage_->epoch_history_.emplace_back(std::move(storage_->epoch_id_), storage_->last_commit_timestamp_);
|
||||
storage_->epoch_id_ = std::move(*maybe_epoch_id);
|
||||
}
|
||||
|
||||
if (storage_->wal_file_) {
|
||||
if (req.seq_num > storage_->wal_file_->SequenceNumber() || *maybe_epoch_id != storage_->epoch_id_) {
|
||||
storage_->wal_file_->FinalizeWal();
|
||||
storage_->wal_file_.reset();
|
||||
storage_->wal_seq_num_ = req.seq_num;
|
||||
} else {
|
||||
MG_ASSERT(storage_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file");
|
||||
storage_->wal_seq_num_ = req.seq_num + 1;
|
||||
}
|
||||
} else {
|
||||
storage_->wal_seq_num_ = req.seq_num;
|
||||
}
|
||||
|
||||
if (req.previous_commit_timestamp != storage_->last_commit_timestamp_.load()) {
|
||||
// Empty the stream
|
||||
bool transaction_complete = false;
|
||||
while (!transaction_complete) {
|
||||
SPDLOG_INFO("Skipping delta");
|
||||
const auto [timestamp, delta] = ReadDelta(&decoder);
|
||||
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
|
||||
}
|
||||
|
||||
replication::AppendDeltasRes res{false, storage_->last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadAndApplyDelta(&decoder);
|
||||
|
||||
replication::AppendDeltasRes res{true, storage_->last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::SnapshotReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
replication::Decoder decoder(req_reader);
|
||||
|
||||
utils::EnsureDirOrDie(storage_->snapshot_directory_);
|
||||
|
||||
const auto maybe_snapshot_path = decoder.ReadFile(storage_->snapshot_directory_);
|
||||
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
|
||||
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
|
||||
|
||||
std::unique_lock<utils::RWLock> storage_guard(storage_->main_lock_);
|
||||
// Clear the database
|
||||
storage_->vertices_.clear();
|
||||
storage_->edges_.clear();
|
||||
|
||||
storage_->constraints_ = Constraints();
|
||||
storage_->indices_.label_index =
|
||||
LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items, storage_->schema_validator_);
|
||||
storage_->indices_.label_property_index = LabelPropertyIndex(&storage_->indices_, &storage_->constraints_,
|
||||
storage_->config_.items, storage_->schema_validator_);
|
||||
try {
|
||||
spdlog::debug("Loading snapshot");
|
||||
auto recovered_snapshot = durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_,
|
||||
&storage_->epoch_history_, &storage_->name_id_mapper_,
|
||||
&storage_->edge_count_, storage_->config_.items);
|
||||
spdlog::debug("Snapshot loaded successfully");
|
||||
// If this step is present it should always be the first step of
|
||||
// the recovery so we use the UUID we read from snasphost
|
||||
storage_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid);
|
||||
storage_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id);
|
||||
const auto &recovery_info = recovered_snapshot.recovery_info;
|
||||
storage_->vertex_id_ = recovery_info.next_vertex_id;
|
||||
storage_->edge_id_ = recovery_info.next_edge_id;
|
||||
storage_->timestamp_ = std::max(storage_->timestamp_, recovery_info.next_timestamp);
|
||||
|
||||
durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage_->indices_,
|
||||
&storage_->constraints_, &storage_->vertices_);
|
||||
} catch (const durability::RecoveryFailure &e) {
|
||||
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
|
||||
}
|
||||
storage_guard.unlock();
|
||||
|
||||
replication::SnapshotRes res{true, storage_->last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
|
||||
// Delete other durability files
|
||||
auto snapshot_files = durability::GetSnapshotFiles(storage_->snapshot_directory_, storage_->uuid_);
|
||||
for (const auto &[path, uuid, _] : snapshot_files) {
|
||||
if (path != *maybe_snapshot_path) {
|
||||
storage_->file_retainer_.DeleteFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
auto wal_files = durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_);
|
||||
if (wal_files) {
|
||||
for (const auto &wal_file : *wal_files) {
|
||||
storage_->file_retainer_.DeleteFile(wal_file.path);
|
||||
}
|
||||
|
||||
storage_->wal_file_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::WalFilesReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
const auto wal_file_number = req.file_number;
|
||||
spdlog::debug("Received WAL files: {}", wal_file_number);
|
||||
|
||||
replication::Decoder decoder(req_reader);
|
||||
|
||||
utils::EnsureDirOrDie(storage_->wal_directory_);
|
||||
|
||||
for (auto i = 0; i < wal_file_number; ++i) {
|
||||
LoadWal(&decoder);
|
||||
}
|
||||
|
||||
replication::WalFilesRes res{true, storage_->last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::CurrentWalReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
replication::Decoder decoder(req_reader);
|
||||
|
||||
utils::EnsureDirOrDie(storage_->wal_directory_);
|
||||
|
||||
LoadWal(&decoder);
|
||||
|
||||
replication::CurrentWalRes res{true, storage_->last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) {
|
||||
const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory;
|
||||
utils::EnsureDir(temp_wal_directory);
|
||||
auto maybe_wal_path = decoder->ReadFile(temp_wal_directory);
|
||||
MG_ASSERT(maybe_wal_path, "Failed to load WAL!");
|
||||
spdlog::trace("Received WAL saved to {}", *maybe_wal_path);
|
||||
try {
|
||||
auto wal_info = durability::ReadWalInfo(*maybe_wal_path);
|
||||
if (wal_info.seq_num == 0) {
|
||||
storage_->uuid_ = wal_info.uuid;
|
||||
}
|
||||
|
||||
if (wal_info.epoch_id != storage_->epoch_id_) {
|
||||
storage_->epoch_history_.emplace_back(wal_info.epoch_id, storage_->last_commit_timestamp_);
|
||||
storage_->epoch_id_ = std::move(wal_info.epoch_id);
|
||||
}
|
||||
|
||||
if (storage_->wal_file_) {
|
||||
if (storage_->wal_file_->SequenceNumber() != wal_info.seq_num) {
|
||||
storage_->wal_file_->FinalizeWal();
|
||||
storage_->wal_seq_num_ = wal_info.seq_num;
|
||||
storage_->wal_file_.reset();
|
||||
}
|
||||
} else {
|
||||
storage_->wal_seq_num_ = wal_info.seq_num;
|
||||
}
|
||||
|
||||
durability::Decoder wal;
|
||||
const auto version = wal.Initialize(*maybe_wal_path, durability::kWalMagic);
|
||||
if (!version) throw durability::RecoveryFailure("Couldn't read WAL magic and/or version!");
|
||||
if (!durability::IsVersionSupported(*version)) throw durability::RecoveryFailure("Invalid WAL version!");
|
||||
wal.SetPosition(wal_info.offset_deltas);
|
||||
|
||||
for (size_t i = 0; i < wal_info.num_deltas;) {
|
||||
i += ReadAndApplyDelta(&wal);
|
||||
}
|
||||
|
||||
spdlog::debug("{} loaded successfully", *maybe_wal_path);
|
||||
} catch (const durability::RecoveryFailure &e) {
|
||||
LOG_FATAL("Couldn't recover WAL deltas from {} because of: {}", *maybe_wal_path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
Storage::ReplicationServer::~ReplicationServer() {
|
||||
if (rpc_server_) {
|
||||
rpc_server_->Shutdown();
|
||||
rpc_server_->AwaitShutdown();
|
||||
}
|
||||
}
|
||||
uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) {
|
||||
auto edge_acc = storage_->edges_.access();
|
||||
auto vertex_acc = storage_->vertices_.access();
|
||||
|
||||
std::optional<std::pair<uint64_t, Storage::Accessor>> commit_timestamp_and_accessor;
|
||||
auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) {
|
||||
if (!commit_timestamp_and_accessor) {
|
||||
commit_timestamp_and_accessor.emplace(commit_timestamp, storage_->Access());
|
||||
} else if (commit_timestamp_and_accessor->first != commit_timestamp) {
|
||||
throw utils::BasicException("Received more than one transaction!");
|
||||
}
|
||||
return &commit_timestamp_and_accessor->second;
|
||||
};
|
||||
|
||||
uint64_t applied_deltas = 0;
|
||||
auto max_commit_timestamp = storage_->last_commit_timestamp_.load();
|
||||
|
||||
for (bool transaction_complete = false; !transaction_complete; ++applied_deltas) {
|
||||
const auto [timestamp, delta] = ReadDelta(decoder);
|
||||
if (timestamp > max_commit_timestamp) {
|
||||
max_commit_timestamp = timestamp;
|
||||
}
|
||||
|
||||
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
|
||||
|
||||
if (timestamp < storage_->timestamp_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SPDLOG_INFO(" Delta {}", applied_deltas);
|
||||
switch (delta.type) {
|
||||
case durability::WalDeltaData::Type::VERTEX_CREATE: {
|
||||
spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
transaction->CreateVertex(delta.vertex_create_delete.gid);
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::VERTEX_DELETE: {
|
||||
spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = transaction->DeleteVertex(&*vertex);
|
||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: {
|
||||
spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(),
|
||||
delta.vertex_add_remove_label.label);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
||||
spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(),
|
||||
delta.vertex_add_remove_label.label);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: {
|
||||
spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
|
||||
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||
delta.vertex_edge_set_property.value);
|
||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::EDGE_CREATE: {
|
||||
spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}",
|
||||
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
|
||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
||||
if (!to_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto edge = transaction->CreateEdge(&*from_vertex, &*to_vertex,
|
||||
transaction->NameToEdgeType(delta.edge_create_delete.edge_type),
|
||||
delta.edge_create_delete.gid);
|
||||
if (edge.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::EDGE_DELETE: {
|
||||
spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}",
|
||||
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
|
||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
||||
if (!to_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto edges = from_vertex->OutEdges(View::NEW, {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)},
|
||||
&*to_vertex);
|
||||
if (edges.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
if (edges->size() != 1) throw utils::BasicException("Invalid transaction!");
|
||||
auto &edge = (*edges)[0];
|
||||
auto ret = transaction->DeleteEdge(&edge);
|
||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: {
|
||||
spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
|
||||
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
||||
|
||||
if (!storage_->config_.items.properties_on_edges)
|
||||
throw utils::BasicException(
|
||||
"Can't set properties on edges because properties on edges "
|
||||
"are disabled!");
|
||||
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
|
||||
// The following block of code effectively implements `FindEdge` and
|
||||
// yields an accessor that is only valid for managing the edge's
|
||||
// properties.
|
||||
auto edge = edge_acc.find(delta.vertex_edge_set_property.gid);
|
||||
if (edge == edge_acc.end()) throw utils::BasicException("Invalid transaction!");
|
||||
// The edge visibility check must be done here manually because we
|
||||
// don't allow direct access to the edges through the public API.
|
||||
{
|
||||
bool is_visible = true;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge->lock);
|
||||
is_visible = !edge->deleted;
|
||||
delta = edge->delta;
|
||||
}
|
||||
ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, [&is_visible](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
is_visible = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
is_visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!is_visible) throw utils::BasicException("Invalid transaction!");
|
||||
}
|
||||
EdgeRef edge_ref(&*edge);
|
||||
// Here we create an edge accessor that we will use to get the
|
||||
// properties of the edge. The accessor is created with an invalid
|
||||
// type and invalid from/to pointers because we don't know them
|
||||
// here, but that isn't an issue because we won't use that part of
|
||||
// the API here.
|
||||
auto ea = EdgeAccessor{edge_ref,
|
||||
EdgeTypeId::FromUint(0UL),
|
||||
nullptr,
|
||||
nullptr,
|
||||
&transaction->transaction_,
|
||||
&storage_->indices_,
|
||||
&storage_->constraints_,
|
||||
storage_->config_.items,
|
||||
storage_->schema_validator_};
|
||||
|
||||
auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||
delta.vertex_edge_set_property.value);
|
||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
|
||||
case durability::WalDeltaData::Type::TRANSACTION_END: {
|
||||
spdlog::trace(" Transaction end");
|
||||
if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
|
||||
throw utils::BasicException("Invalid data!");
|
||||
auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first);
|
||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
||||
commit_timestamp_and_accessor = std::nullopt;
|
||||
break;
|
||||
}
|
||||
|
||||
case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: {
|
||||
spdlog::trace(" Create label index on :{}", delta.operation_label.label);
|
||||
// Need to send the timestamp
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp))
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::LABEL_INDEX_DROP: {
|
||||
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp))
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
|
||||
spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label,
|
||||
delta.operation_label_property.property);
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label),
|
||||
storage_->NameToProperty(delta.operation_label_property.property), timestamp))
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
|
||||
spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label,
|
||||
delta.operation_label_property.property);
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label_property.label),
|
||||
storage_->NameToProperty(delta.operation_label_property.property), timestamp))
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
|
||||
spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label,
|
||||
delta.operation_label_property.property);
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = storage_->CreateExistenceConstraint(
|
||||
storage_->NameToLabel(delta.operation_label_property.label),
|
||||
storage_->NameToProperty(delta.operation_label_property.property), timestamp);
|
||||
if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
|
||||
spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label,
|
||||
delta.operation_label_property.property);
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
if (!storage_->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label),
|
||||
storage_->NameToProperty(delta.operation_label_property.property),
|
||||
timestamp))
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
|
||||
std::stringstream ss;
|
||||
utils::PrintIterable(ss, delta.operation_label_properties.properties);
|
||||
spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
std::set<PropertyId> properties;
|
||||
for (const auto &prop : delta.operation_label_properties.properties) {
|
||||
properties.emplace(storage_->NameToProperty(prop));
|
||||
}
|
||||
auto ret = storage_->CreateUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label),
|
||||
properties, timestamp);
|
||||
if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
|
||||
throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
|
||||
std::stringstream ss;
|
||||
utils::PrintIterable(ss, delta.operation_label_properties.properties);
|
||||
spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
|
||||
std::set<PropertyId> properties;
|
||||
for (const auto &prop : delta.operation_label_properties.properties) {
|
||||
properties.emplace(storage_->NameToProperty(prop));
|
||||
}
|
||||
auto ret = storage_->DropUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label),
|
||||
properties, timestamp);
|
||||
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid data!");
|
||||
|
||||
storage_->last_commit_timestamp_ = max_commit_timestamp;
|
||||
|
||||
return applied_deltas;
|
||||
}
|
||||
} // namespace memgraph::storage::v3
|
@ -1,47 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/storage.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class Storage::ReplicationServer {
|
||||
public:
|
||||
explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint,
|
||||
const replication::ReplicationServerConfig &config);
|
||||
ReplicationServer(const ReplicationServer &) = delete;
|
||||
ReplicationServer(ReplicationServer &&) = delete;
|
||||
ReplicationServer &operator=(const ReplicationServer &) = delete;
|
||||
ReplicationServer &operator=(ReplicationServer &&) = delete;
|
||||
|
||||
~ReplicationServer();
|
||||
|
||||
private:
|
||||
// RPC handlers
|
||||
void HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
static void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
|
||||
void LoadWal(replication::Decoder *decoder);
|
||||
uint64_t ReadAndApplyDelta(durability::BaseDecoder *decoder);
|
||||
|
||||
std::optional<communication::ServerContext> rpc_server_context_;
|
||||
std::optional<rpc::Server> rpc_server_;
|
||||
|
||||
Storage *storage_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -1,74 +0,0 @@
|
||||
;; 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.
|
||||
|
||||
#>cpp
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "rpc/messages.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
cpp<#
|
||||
(lcp:namespace memgraph)
|
||||
(lcp:namespace storage)
|
||||
(lcp:namespace v3)
|
||||
(lcp:namespace replication)
|
||||
|
||||
(lcp:define-rpc append-deltas
|
||||
;; The actual deltas are sent as additional data using the RPC client's
|
||||
;; streaming API for additional data.
|
||||
(:request
|
||||
((previous-commit-timestamp :uint64_t)
|
||||
(seq-num :uint64_t)))
|
||||
(:response
|
||||
((success :bool)
|
||||
(current-commit-timestamp :uint64_t))))
|
||||
|
||||
(lcp:define-rpc heartbeat
|
||||
(:request
|
||||
((main-commit-timestamp :uint64_t)
|
||||
(epoch-id "std::string")))
|
||||
(:response
|
||||
((success :bool)
|
||||
(current-commit-timestamp :uint64_t)
|
||||
(epoch-id "std::string"))))
|
||||
|
||||
;; FrequentHearthbeat is required because calling Heartbeat takes the storage lock.
|
||||
;; Configured by `replication_replica_check_delay`.
|
||||
(lcp:define-rpc frequent-heartbeat
|
||||
(:request ())
|
||||
(:response ((success :bool))))
|
||||
|
||||
(lcp:define-rpc snapshot
|
||||
(:request ())
|
||||
(:response
|
||||
((success :bool)
|
||||
(current-commit-timestamp :uint64_t))))
|
||||
|
||||
(lcp:define-rpc wal-files
|
||||
(:request ((file-number :uint64_t)))
|
||||
(:response
|
||||
((success :bool)
|
||||
(current-commit-timestamp :uint64_t))))
|
||||
|
||||
(lcp:define-rpc current-wal
|
||||
(:request ())
|
||||
(:response
|
||||
((success :bool)
|
||||
(current-commit-timestamp :uint64_t))))
|
||||
|
||||
(lcp:pop-namespace) ;; replication
|
||||
(lcp:pop-namespace) ;; v3
|
||||
(lcp:pop-namespace) ;; storage
|
||||
(lcp:pop-namespace) ;; memgraph
|
@ -1,149 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/replication/serialization.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::replication {
|
||||
////// Encoder //////
|
||||
void Encoder::WriteMarker(durability::Marker marker) { slk::Save(marker, builder_); }
|
||||
|
||||
void Encoder::WriteBool(bool value) {
|
||||
WriteMarker(durability::Marker::TYPE_BOOL);
|
||||
slk::Save(value, builder_);
|
||||
}
|
||||
|
||||
void Encoder::WriteUint(uint64_t value) {
|
||||
WriteMarker(durability::Marker::TYPE_INT);
|
||||
slk::Save(value, builder_);
|
||||
}
|
||||
|
||||
void Encoder::WriteDouble(double value) {
|
||||
WriteMarker(durability::Marker::TYPE_DOUBLE);
|
||||
slk::Save(value, builder_);
|
||||
}
|
||||
|
||||
void Encoder::WriteString(const std::string_view &value) {
|
||||
WriteMarker(durability::Marker::TYPE_STRING);
|
||||
slk::Save(value, builder_);
|
||||
}
|
||||
|
||||
void Encoder::WritePropertyValue(const PropertyValue &value) {
|
||||
WriteMarker(durability::Marker::TYPE_PROPERTY_VALUE);
|
||||
slk::Save(value, builder_);
|
||||
}
|
||||
|
||||
void Encoder::WriteBuffer(const uint8_t *buffer, const size_t buffer_size) { builder_->Save(buffer, buffer_size); }
|
||||
|
||||
void Encoder::WriteFileData(utils::InputFile *file) {
|
||||
auto file_size = file->GetSize();
|
||||
uint8_t buffer[utils::kFileBufferSize];
|
||||
while (file_size > 0) {
|
||||
const auto chunk_size = std::min(file_size, utils::kFileBufferSize);
|
||||
file->Read(buffer, chunk_size);
|
||||
WriteBuffer(buffer, chunk_size);
|
||||
file_size -= chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
void Encoder::WriteFile(const std::filesystem::path &path) {
|
||||
utils::InputFile file;
|
||||
MG_ASSERT(file.Open(path), "Failed to open file {}", path);
|
||||
MG_ASSERT(path.has_filename(), "Path does not have a filename!");
|
||||
const auto &filename = path.filename().generic_string();
|
||||
WriteString(filename);
|
||||
auto file_size = file.GetSize();
|
||||
WriteUint(file_size);
|
||||
WriteFileData(&file);
|
||||
file.Close();
|
||||
}
|
||||
|
||||
////// Decoder //////
|
||||
std::optional<durability::Marker> Decoder::ReadMarker() {
|
||||
durability::Marker marker{durability::Marker::TYPE_NULL};
|
||||
slk::Load(&marker, reader_);
|
||||
return marker;
|
||||
}
|
||||
|
||||
std::optional<bool> Decoder::ReadBool() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_BOOL) return std::nullopt;
|
||||
bool value{false};
|
||||
slk::Load(&value, reader_);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> Decoder::ReadUint() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_INT) return std::nullopt;
|
||||
uint64_t value{0};
|
||||
slk::Load(&value, reader_);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<double> Decoder::ReadDouble() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_DOUBLE) return std::nullopt;
|
||||
double value{0.0};
|
||||
slk::Load(&value, reader_);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<std::string> Decoder::ReadString() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_STRING) return std::nullopt;
|
||||
std::string value;
|
||||
slk::Load(&value, reader_);
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
std::optional<PropertyValue> Decoder::ReadPropertyValue() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_PROPERTY_VALUE)
|
||||
return std::nullopt;
|
||||
PropertyValue value;
|
||||
slk::Load(&value, reader_);
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
bool Decoder::SkipString() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_STRING) return false;
|
||||
std::string value;
|
||||
slk::Load(&value, reader_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Decoder::SkipPropertyValue() {
|
||||
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_PROPERTY_VALUE) return false;
|
||||
PropertyValue value;
|
||||
slk::Load(&value, reader_);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> Decoder::ReadFile(const std::filesystem::path &directory,
|
||||
const std::string &suffix) {
|
||||
MG_ASSERT(std::filesystem::exists(directory) && std::filesystem::is_directory(directory),
|
||||
"Sent path for streamed files should be a valid directory!");
|
||||
utils::OutputFile file;
|
||||
const auto maybe_filename = ReadString();
|
||||
MG_ASSERT(maybe_filename, "Filename missing for the file");
|
||||
const auto filename = *maybe_filename + suffix;
|
||||
auto path = directory / filename;
|
||||
|
||||
file.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING);
|
||||
std::optional<size_t> maybe_file_size = ReadUint();
|
||||
MG_ASSERT(maybe_file_size, "File size missing");
|
||||
auto file_size = *maybe_file_size;
|
||||
uint8_t buffer[utils::kFileBufferSize];
|
||||
while (file_size > 0) {
|
||||
const auto chunk_size = std::min(file_size, utils::kFileBufferSize);
|
||||
reader_->Load(buffer, chunk_size);
|
||||
file.Write(buffer, chunk_size);
|
||||
file_size -= chunk_size;
|
||||
}
|
||||
file.Close();
|
||||
return std::move(path);
|
||||
}
|
||||
} // namespace memgraph::storage::v3::replication
|
@ -1,80 +0,0 @@
|
||||
// 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 <filesystem>
|
||||
|
||||
#include "slk/streams.hpp"
|
||||
#include "storage/v3/durability/serialization.hpp"
|
||||
#include "storage/v3/replication/slk.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/file.hpp"
|
||||
|
||||
namespace memgraph::storage::v3::replication {
|
||||
|
||||
class Encoder final : public durability::BaseEncoder {
|
||||
public:
|
||||
explicit Encoder(slk::Builder *builder) : builder_(builder) {}
|
||||
|
||||
void WriteMarker(durability::Marker marker) override;
|
||||
|
||||
void WriteBool(bool value) override;
|
||||
|
||||
void WriteUint(uint64_t value) override;
|
||||
|
||||
void WriteDouble(double value) override;
|
||||
|
||||
void WriteString(const std::string_view &value) override;
|
||||
|
||||
void WritePropertyValue(const PropertyValue &value) override;
|
||||
|
||||
void WriteBuffer(const uint8_t *buffer, size_t buffer_size);
|
||||
|
||||
void WriteFileData(utils::InputFile *file);
|
||||
|
||||
void WriteFile(const std::filesystem::path &path);
|
||||
|
||||
private:
|
||||
slk::Builder *builder_;
|
||||
};
|
||||
|
||||
class Decoder final : public durability::BaseDecoder {
|
||||
public:
|
||||
explicit Decoder(slk::Reader *reader) : reader_(reader) {}
|
||||
|
||||
std::optional<durability::Marker> ReadMarker() override;
|
||||
|
||||
std::optional<bool> ReadBool() override;
|
||||
|
||||
std::optional<uint64_t> ReadUint() override;
|
||||
|
||||
std::optional<double> ReadDouble() override;
|
||||
|
||||
std::optional<std::string> ReadString() override;
|
||||
|
||||
std::optional<PropertyValue> ReadPropertyValue() override;
|
||||
|
||||
bool SkipString() override;
|
||||
|
||||
bool SkipPropertyValue() override;
|
||||
|
||||
/// Read the file and save it inside the specified directory.
|
||||
/// @param directory Directory which will contain the read file.
|
||||
/// @param suffix Suffix to be added to the received file's filename.
|
||||
/// @return If the read was successful, path to the read file.
|
||||
std::optional<std::filesystem::path> ReadFile(const std::filesystem::path &directory, const std::string &suffix = "");
|
||||
|
||||
private:
|
||||
slk::Reader *reader_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3::replication
|
@ -1,169 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/replication/slk.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
void Save(const storage::v3::Gid &gid, slk::Builder *builder) { slk::Save(gid.AsUint(), builder); }
|
||||
|
||||
void Load(storage::v3::Gid *gid, slk::Reader *reader) {
|
||||
uint64_t value{0};
|
||||
slk::Load(&value, reader);
|
||||
*gid = storage::v3::Gid::FromUint(value);
|
||||
}
|
||||
|
||||
void Load(storage::v3::PropertyValue::Type *type, slk::Reader *reader) {
|
||||
using PVTypeUnderlyingType = std::underlying_type_t<storage::v3::PropertyValue::Type>;
|
||||
PVTypeUnderlyingType value{};
|
||||
slk::Load(&value, reader);
|
||||
bool valid{false};
|
||||
switch (value) {
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Null):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Bool):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Int):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Double):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::String):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::List):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Map):
|
||||
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::TemporalData):
|
||||
valid = true;
|
||||
break;
|
||||
default:
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (!valid) throw slk::SlkDecodeException("Trying to load unknown storage::v3::PropertyValue!");
|
||||
*type = static_cast<storage::v3::PropertyValue::Type>(value);
|
||||
}
|
||||
|
||||
void Save(const storage::v3::PropertyValue &value, slk::Builder *builder) {
|
||||
switch (value.type()) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
slk::Save(storage::v3::PropertyValue::Type::Null, builder);
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Bool:
|
||||
slk::Save(storage::v3::PropertyValue::Type::Bool, builder);
|
||||
slk::Save(value.ValueBool(), builder);
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Int:
|
||||
slk::Save(storage::v3::PropertyValue::Type::Int, builder);
|
||||
slk::Save(value.ValueInt(), builder);
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Double:
|
||||
slk::Save(storage::v3::PropertyValue::Type::Double, builder);
|
||||
slk::Save(value.ValueDouble(), builder);
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::String:
|
||||
slk::Save(storage::v3::PropertyValue::Type::String, builder);
|
||||
slk::Save(value.ValueString(), builder);
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::List: {
|
||||
slk::Save(storage::v3::PropertyValue::Type::List, builder);
|
||||
const auto &values = value.ValueList();
|
||||
size_t size = values.size();
|
||||
slk::Save(size, builder);
|
||||
for (const auto &v : values) {
|
||||
slk::Save(v, builder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Map: {
|
||||
slk::Save(storage::v3::PropertyValue::Type::Map, builder);
|
||||
const auto &map = value.ValueMap();
|
||||
size_t size = map.size();
|
||||
slk::Save(size, builder);
|
||||
for (const auto &kv : map) {
|
||||
slk::Save(kv, builder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::TemporalData: {
|
||||
slk::Save(storage::v3::PropertyValue::Type::TemporalData, builder);
|
||||
const auto temporal_data = value.ValueTemporalData();
|
||||
slk::Save(temporal_data.type, builder);
|
||||
slk::Save(temporal_data.microseconds, builder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Load(storage::v3::PropertyValue *value, slk::Reader *reader) {
|
||||
storage::v3::PropertyValue::Type type{};
|
||||
slk::Load(&type, reader);
|
||||
switch (type) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
*value = storage::v3::PropertyValue();
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Bool: {
|
||||
bool v{false};
|
||||
slk::Load(&v, reader);
|
||||
*value = storage::v3::PropertyValue(v);
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Int: {
|
||||
int64_t v{0};
|
||||
slk::Load(&v, reader);
|
||||
*value = storage::v3::PropertyValue(v);
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Double: {
|
||||
double v{0.0};
|
||||
slk::Load(&v, reader);
|
||||
*value = storage::v3::PropertyValue(v);
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::String: {
|
||||
std::string v;
|
||||
slk::Load(&v, reader);
|
||||
*value = storage::v3::PropertyValue(std::move(v));
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::List: {
|
||||
size_t size{0};
|
||||
slk::Load(&size, reader);
|
||||
std::vector<storage::v3::PropertyValue> list(size);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
slk::Load(&list[i], reader);
|
||||
}
|
||||
*value = storage::v3::PropertyValue(std::move(list));
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Map: {
|
||||
size_t size{0};
|
||||
slk::Load(&size, reader);
|
||||
std::map<std::string, storage::v3::PropertyValue> map;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
std::pair<std::string, storage::v3::PropertyValue> kv;
|
||||
slk::Load(&kv, reader);
|
||||
map.insert(kv);
|
||||
}
|
||||
*value = storage::v3::PropertyValue(std::move(map));
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::TemporalData: {
|
||||
storage::v3::TemporalType temporal_type{};
|
||||
slk::Load(&temporal_type, reader);
|
||||
int64_t microseconds{0};
|
||||
slk::Load(µseconds, reader);
|
||||
*value = storage::v3::PropertyValue(storage::v3::TemporalData{temporal_type, microseconds});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::slk
|
@ -1,41 +0,0 @@
|
||||
// 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 "slk/serialization.hpp"
|
||||
#include "storage/v3/durability/marker.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
void Save(const storage::v3::Gid &gid, slk::Builder *builder);
|
||||
void Load(storage::v3::Gid *gid, slk::Reader *reader);
|
||||
|
||||
void Save(const storage::v3::PropertyValue &value, slk::Builder *builder);
|
||||
void Load(storage::v3::PropertyValue *value, slk::Reader *reader);
|
||||
|
||||
template <utils::Enum T>
|
||||
void Save(const T &enum_value, slk::Builder *builder) {
|
||||
slk::Save(utils::UnderlyingCast(enum_value), builder);
|
||||
}
|
||||
|
||||
template <utils::Enum T>
|
||||
void Load(T *enum_value, slk::Reader *reader) {
|
||||
using UnderlyingType = std::underlying_type_t<T>;
|
||||
UnderlyingType value;
|
||||
slk::Load(&value, reader);
|
||||
*enum_value = static_cast<T>(value);
|
||||
}
|
||||
|
||||
} // namespace memgraph::slk
|
@ -98,9 +98,24 @@ SchemaValidator::SchemaValidator(Schemas &schemas) : schemas_{schemas} {}
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidateLabelUpdate(const LabelId label) const {
|
||||
const auto *schema = schemas_.GetSchema(label);
|
||||
if (schema) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label);
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const LabelId primary_label)
|
||||
: schema_validator{&schema_validator}, primary_label_{primary_label} {}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidatePropertyUpdate(PropertyId property_id) const {
|
||||
return schema_validator->ValidatePropertyUpdate(primary_label_, property_id);
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidateAddLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidateRemoveLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -27,7 +27,7 @@ struct SchemaViolation {
|
||||
NO_SCHEMA_DEFINED_FOR_LABEL,
|
||||
VERTEX_PROPERTY_WRONG_TYPE,
|
||||
VERTEX_UPDATE_PRIMARY_KEY,
|
||||
VERTEX_MODIFY_PRIMARY_LABEL,
|
||||
VERTEX_UPDATE_PRIMARY_LABEL,
|
||||
VERTEX_SECONDARY_LABEL_IS_PRIMARY,
|
||||
};
|
||||
|
||||
@ -63,6 +63,20 @@ class SchemaValidator {
|
||||
Schemas &schemas_;
|
||||
};
|
||||
|
||||
struct VertexValidator {
|
||||
explicit VertexValidator(const SchemaValidator &schema_validator, LabelId primary_label);
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidatePropertyUpdate(PropertyId property_id) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateAddLabel(LabelId label) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateRemoveLabel(LabelId label) const;
|
||||
|
||||
const SchemaValidator *schema_validator;
|
||||
|
||||
LabelId primary_label_;
|
||||
};
|
||||
|
||||
template <typename TValue>
|
||||
using ResultSchema = utils::BasicResult<std::variant<SchemaViolation, Error>, TValue>;
|
||||
|
||||
|
@ -11,10 +11,12 @@
|
||||
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
@ -47,6 +49,15 @@ bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<Schema
|
||||
|
||||
bool Schemas::DropSchema(const LabelId primary_label) { return schemas_.erase(primary_label); }
|
||||
|
||||
bool Schemas::IsPropertyKey(const LabelId primary_label, const PropertyId property_id) const {
|
||||
if (const auto schema = schemas_.find(primary_label); schema != schemas_.end()) {
|
||||
return std::ranges::find_if(schema->second, [property_id](const auto &elem) {
|
||||
return elem.property_id == property_id;
|
||||
}) != schema->second.end();
|
||||
}
|
||||
throw utils::BasicException("Schema not found!");
|
||||
}
|
||||
|
||||
std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value) {
|
||||
switch (property_value.type()) {
|
||||
case PropertyValue::Type::Bool: {
|
||||
|
@ -53,11 +53,15 @@ class Schemas {
|
||||
|
||||
// Returns true if it was successfully created or false if the schema
|
||||
// already exists
|
||||
[[nodiscard]] bool CreateSchema(LabelId label, const std::vector<SchemaProperty> &schemas_types);
|
||||
[[nodiscard]] bool CreateSchema(LabelId primary_label, const std::vector<SchemaProperty> &schemas_types);
|
||||
|
||||
// Returns true if it was successfully dropped or false if the schema
|
||||
// does not exist
|
||||
[[nodiscard]] bool DropSchema(LabelId label);
|
||||
[[nodiscard]] bool DropSchema(LabelId primary_label);
|
||||
|
||||
// Returns true if property is part of schema defined
|
||||
// by primary label
|
||||
[[nodiscard]] bool IsPropertyKey(LabelId primary_label, PropertyId property_id) const;
|
||||
|
||||
private:
|
||||
SchemasMap schemas_;
|
||||
|
1100
src/storage/v3/shard.cpp
Normal file
1100
src/storage/v3/shard.cpp
Normal file
File diff suppressed because it is too large
Load Diff
449
src/storage/v3/shard.hpp
Normal file
449
src/storage/v3/shard.hpp
Normal file
@ -0,0 +1,449 @@
|
||||
// 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 <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "storage/v3/vertex_id.hpp"
|
||||
#include "storage/v3/vertices_skip_list.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// The storage is based on this paper:
|
||||
// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf
|
||||
// The paper implements a fully serializable storage, in our implementation we
|
||||
// only implement snapshot isolation for transactions.
|
||||
|
||||
/// Iterable for iterating through all vertices of a Storage.
|
||||
///
|
||||
/// An instance of this will be usually be wrapped inside VerticesIterable for
|
||||
/// generic, public use.
|
||||
class AllVerticesIterable final {
|
||||
VerticesSkipList::Accessor vertices_accessor_;
|
||||
Transaction *transaction_;
|
||||
View view_;
|
||||
Indices *indices_;
|
||||
Config::Items config_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
const Schemas *schemas_;
|
||||
std::optional<VertexAccessor> vertex_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
AllVerticesIterable *self_;
|
||||
VerticesSkipList::Iterator it_;
|
||||
|
||||
public:
|
||||
Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it);
|
||||
|
||||
VertexAccessor operator*() const;
|
||||
|
||||
Iterator &operator++();
|
||||
|
||||
bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||
Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
|
||||
: vertices_accessor_(std::move(vertices_accessor)),
|
||||
transaction_(transaction),
|
||||
view_(view),
|
||||
indices_(indices),
|
||||
config_(config),
|
||||
vertex_validator_{&vertex_validator} {}
|
||||
|
||||
Iterator begin() { return {this, vertices_accessor_.begin()}; }
|
||||
Iterator end() { return {this, vertices_accessor_.end()}; }
|
||||
};
|
||||
|
||||
/// Generic access to different kinds of vertex iterations.
|
||||
///
|
||||
/// This class should be the primary type used by the client code to iterate
|
||||
/// over vertices inside a Storage instance.
|
||||
class VerticesIterable final {
|
||||
enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
||||
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable all_vertices_;
|
||||
LabelIndex::Iterable vertices_by_label_;
|
||||
LabelPropertyIndex::Iterable vertices_by_label_property_;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit VerticesIterable(AllVerticesIterable);
|
||||
explicit VerticesIterable(LabelIndex::Iterable);
|
||||
explicit VerticesIterable(LabelPropertyIndex::Iterable);
|
||||
|
||||
VerticesIterable(const VerticesIterable &) = delete;
|
||||
VerticesIterable &operator=(const VerticesIterable &) = delete;
|
||||
|
||||
VerticesIterable(VerticesIterable &&) noexcept;
|
||||
VerticesIterable &operator=(VerticesIterable &&) noexcept;
|
||||
|
||||
~VerticesIterable();
|
||||
|
||||
class Iterator final {
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable::Iterator all_it_;
|
||||
LabelIndex::Iterable::Iterator by_label_it_;
|
||||
LabelPropertyIndex::Iterable::Iterator by_label_property_it_;
|
||||
};
|
||||
|
||||
void Destroy() noexcept;
|
||||
|
||||
public:
|
||||
explicit Iterator(AllVerticesIterable::Iterator);
|
||||
explicit Iterator(LabelIndex::Iterable::Iterator);
|
||||
explicit Iterator(LabelPropertyIndex::Iterable::Iterator);
|
||||
|
||||
Iterator(const Iterator &);
|
||||
Iterator &operator=(const Iterator &);
|
||||
|
||||
Iterator(Iterator &&) noexcept;
|
||||
Iterator &operator=(Iterator &&) noexcept;
|
||||
|
||||
~Iterator();
|
||||
|
||||
VertexAccessor operator*() const;
|
||||
|
||||
Iterator &operator++();
|
||||
|
||||
bool operator==(const Iterator &other) const;
|
||||
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
Iterator begin();
|
||||
Iterator end();
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing indices in the storage.
|
||||
struct IndicesInfo {
|
||||
std::vector<LabelId> label;
|
||||
std::vector<std::pair<LabelId, PropertyId>> label_property;
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing schemas in the storage
|
||||
struct SchemasInfo {
|
||||
Schemas::SchemasList schemas;
|
||||
};
|
||||
|
||||
/// Structure used to return information about the storage.
|
||||
struct StorageInfo {
|
||||
uint64_t vertex_count;
|
||||
uint64_t edge_count;
|
||||
double average_degree;
|
||||
uint64_t memory_usage;
|
||||
};
|
||||
|
||||
class Shard final {
|
||||
public:
|
||||
/// @throw std::system_error
|
||||
/// @throw std::bad_alloc
|
||||
explicit Shard(LabelId primary_label, PrimaryKey min_primary_key, std::optional<PrimaryKey> max_primary_key,
|
||||
Config config = Config());
|
||||
|
||||
Shard(const Shard &) = delete;
|
||||
Shard(Shard &&) noexcept = delete;
|
||||
Shard &operator=(const Shard &) = delete;
|
||||
Shard operator=(Shard &&) noexcept = delete;
|
||||
~Shard();
|
||||
|
||||
class Accessor final {
|
||||
private:
|
||||
friend class Shard;
|
||||
|
||||
Accessor(Shard &shard, Transaction &transaction);
|
||||
|
||||
public:
|
||||
// TODO(gvolfing) this is just a workaround for stitching remove this later.
|
||||
LabelId GetPrimaryLabel() const noexcept { return shard_->primary_label_; }
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<VertexAccessor> CreateVertexAndValidate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<VertexAccessor> CreateVertexAndValidate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels, const std::vector<PropertyValue> &primary_properties,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties);
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(std::vector<PropertyValue> primary_key, View view);
|
||||
|
||||
VerticesIterable Vertices(View view) {
|
||||
return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), transaction_, view, &shard_->indices_,
|
||||
shard_->config_.items, shard_->vertex_validator_));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(LabelId label, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view);
|
||||
|
||||
/// Return approximate number of all vertices in the database.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount() const { return static_cast<int64_t>(shard_->vertices_.size()); }
|
||||
|
||||
/// Return approximate number of vertices with the given label.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label) const {
|
||||
return shard_->indices_.label_index.ApproximateVertexCount(label);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and property.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
return shard_->indices_.label_property_index.ApproximateVertexCount(label, property);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and the given
|
||||
/// value for the given property. Note that this is always an over-estimate
|
||||
/// and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const {
|
||||
return shard_->indices_.label_property_index.ApproximateVertexCount(label, property, value);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and value for
|
||||
/// the given property in the range defined by provided upper and lower
|
||||
/// bounds.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper) const {
|
||||
return shard_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
|
||||
}
|
||||
|
||||
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex);
|
||||
|
||||
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
||||
VertexAccessor *vertex);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<EdgeAccessor> CreateEdge(VertexId from_vertex_id, VertexId to_vertex_id, EdgeTypeId edge_type, Gid gid);
|
||||
|
||||
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<EdgeAccessor>> DeleteEdge(VertexId from_vertex_id, VertexId to_vertex_id, Gid edge_id);
|
||||
|
||||
LabelId NameToLabel(std::string_view name) const;
|
||||
|
||||
PropertyId NameToProperty(std::string_view name) const;
|
||||
|
||||
EdgeTypeId NameToEdgeType(std::string_view name) const;
|
||||
|
||||
const std::string &LabelToName(LabelId label) const;
|
||||
|
||||
const std::string &PropertyToName(PropertyId property) const;
|
||||
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
bool LabelIndexExists(LabelId label) const { return shard_->indices_.label_index.IndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const {
|
||||
return shard_->indices_.label_property_index.IndexExists(label, property);
|
||||
}
|
||||
|
||||
IndicesInfo ListAllIndices() const {
|
||||
return {shard_->indices_.label_index.ListIndices(), shard_->indices_.label_property_index.ListIndices()};
|
||||
}
|
||||
|
||||
const SchemaValidator &GetSchemaValidator() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const { return {shard_->schemas_.ListSchemas()}; }
|
||||
|
||||
void AdvanceCommand();
|
||||
|
||||
void Commit(coordinator::Hlc commit_timestamp);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void Abort();
|
||||
|
||||
private:
|
||||
/// @throw std::bad_alloc
|
||||
VertexAccessor CreateVertex(Gid gid, LabelId primary_label);
|
||||
|
||||
Shard *shard_;
|
||||
Transaction *transaction_;
|
||||
Config::Items config_;
|
||||
};
|
||||
|
||||
Accessor Access(coordinator::Hlc start_timestamp, std::optional<IsolationLevel> override_isolation_level = {}) {
|
||||
return Accessor{*this, GetTransaction(start_timestamp, override_isolation_level.value_or(isolation_level_))};
|
||||
}
|
||||
|
||||
LabelId NameToLabel(std::string_view name) const;
|
||||
|
||||
PropertyId NameToProperty(std::string_view name) const;
|
||||
|
||||
EdgeTypeId NameToEdgeType(std::string_view name) const;
|
||||
|
||||
const std::string &LabelToName(LabelId label) const;
|
||||
|
||||
const std::string &PropertyToName(PropertyId property) const;
|
||||
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
bool DropIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
bool DropIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
IndicesInfo ListAllIndices() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const;
|
||||
|
||||
const Schemas::Schema *GetSchema(LabelId primary_label) const;
|
||||
|
||||
bool CreateSchema(LabelId primary_label, const std::vector<SchemaProperty> &schemas_types);
|
||||
|
||||
bool DropSchema(LabelId primary_label);
|
||||
|
||||
StorageInfo GetInfo() const;
|
||||
|
||||
void SetIsolationLevel(IsolationLevel isolation_level);
|
||||
|
||||
// Might invalidate accessors
|
||||
void CollectGarbage(io::Time current_time);
|
||||
|
||||
void StoreMapping(std::unordered_map<uint64_t, std::string> id_to_name);
|
||||
|
||||
private:
|
||||
Transaction &GetTransaction(coordinator::Hlc start_timestamp, IsolationLevel isolation_level);
|
||||
|
||||
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
[[nodiscard]] bool IsVertexBelongToShard(const VertexId &vertex_id) const;
|
||||
|
||||
// Main object storage
|
||||
NameIdMapper name_id_mapper_;
|
||||
LabelId primary_label_;
|
||||
// The shard's range is [min, max)
|
||||
PrimaryKey min_primary_key_;
|
||||
std::optional<PrimaryKey> max_primary_key_;
|
||||
VerticesSkipList vertices_;
|
||||
utils::SkipList<Edge> edges_;
|
||||
// Even though the edge count is already kept in the `edges_` SkipList, the
|
||||
// list is used only when properties are enabled for edges. Because of that we
|
||||
// keep a separate count of edges that is always updated.
|
||||
uint64_t edge_count_{0};
|
||||
|
||||
SchemaValidator schema_validator_;
|
||||
VertexValidator vertex_validator_;
|
||||
Indices indices_;
|
||||
Schemas schemas_;
|
||||
|
||||
std::list<Transaction *> committed_transactions_;
|
||||
IsolationLevel isolation_level_;
|
||||
|
||||
Config config_;
|
||||
|
||||
// Vertices that are logically deleted but still have to be removed from
|
||||
// indices before removing them from the main storage.
|
||||
std::list<PrimaryKey> deleted_vertices_;
|
||||
|
||||
// Edges that are logically deleted and wait to be removed from the main
|
||||
// storage.
|
||||
std::list<Gid> deleted_edges_;
|
||||
|
||||
// UUID used to distinguish snapshots and to link snapshots to WALs
|
||||
std::string uuid_;
|
||||
// Sequence number used to keep track of the chain of WALs.
|
||||
uint64_t wal_seq_num_{0};
|
||||
|
||||
// UUID to distinguish different main instance runs for replication process
|
||||
// on SAME storage.
|
||||
// Multiple instances can have same storage UUID and be MAIN at the same time.
|
||||
// We cannot compare commit timestamps of those instances if one of them
|
||||
// becomes the replica of the other so we use epoch_id_ as additional
|
||||
// discriminating property.
|
||||
// Example of this:
|
||||
// We have 2 instances of the same storage, S1 and S2.
|
||||
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
|
||||
// At the moment when S1 commited a transaction with timestamp 20, and S2
|
||||
// a different transaction with timestamp 15, we change S2's role to REPLICA
|
||||
// and register it on S1.
|
||||
// Without using the epoch_id, we don't know that S1 and S2 have completely
|
||||
// different transactions, we think that the S2 is behind only by 5 commits.
|
||||
std::string epoch_id_;
|
||||
// History of the previous epoch ids.
|
||||
// Each value consists of the epoch id along the last commit belonging to that
|
||||
// epoch.
|
||||
std::deque<std::pair<std::string, uint64_t>> epoch_history_;
|
||||
|
||||
uint64_t wal_unsynced_transactions_{0};
|
||||
|
||||
utils::FileRetainer file_retainer_;
|
||||
|
||||
// Global locker that is used for clients file locking
|
||||
utils::FileRetainer::FileLocker global_locker_;
|
||||
|
||||
// Holds all of the (in progress, committed and aborted) transactions that are read or write to this shard, but
|
||||
// haven't been cleaned up yet
|
||||
std::map<uint64_t, std::unique_ptr<Transaction>> start_logical_id_to_transaction_{};
|
||||
bool has_any_transaction_aborted_since_last_gc{false};
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
407
src/storage/v3/shard_rsm.cpp
Normal file
407
src/storage/v3/shard_rsm.cpp
Normal file
@ -0,0 +1,407 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/shard_rsm.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
using memgraph::msgs::Label;
|
||||
using memgraph::msgs::PropertyId;
|
||||
using memgraph::msgs::Value;
|
||||
using memgraph::msgs::VertexId;
|
||||
|
||||
namespace {
|
||||
// TODO(gvolfing use come algorithm instead of explicit for loops)
|
||||
memgraph::storage::v3::PropertyValue ToPropertyValue(Value &&value) {
|
||||
using PV = memgraph::storage::v3::PropertyValue;
|
||||
PV ret;
|
||||
switch (value.type) {
|
||||
case Value::Type::Null:
|
||||
return PV{};
|
||||
case Value::Type::Bool:
|
||||
return PV(value.bool_v);
|
||||
case Value::Type::Int64:
|
||||
return PV(static_cast<int64_t>(value.int_v));
|
||||
case Value::Type::Double:
|
||||
return PV(value.double_v);
|
||||
case Value::Type::String:
|
||||
return PV(value.string_v);
|
||||
case Value::Type::List: {
|
||||
std::vector<PV> list;
|
||||
for (auto &elem : value.list_v) {
|
||||
list.emplace_back(ToPropertyValue(std::move(elem)));
|
||||
}
|
||||
return PV(list);
|
||||
}
|
||||
case Value::Type::Map: {
|
||||
std::map<std::string, PV> map;
|
||||
for (auto &[key, value] : value.map_v) {
|
||||
map.emplace(std::make_pair(key, ToPropertyValue(std::move(value))));
|
||||
}
|
||||
return PV(map);
|
||||
}
|
||||
// These are not PropertyValues
|
||||
case Value::Type::Vertex:
|
||||
case Value::Type::Edge:
|
||||
case Value::Type::Path:
|
||||
MG_ASSERT(false, "Not PropertyValue");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Value ToValue(const memgraph::storage::v3::PropertyValue &pv) {
|
||||
using memgraph::storage::v3::PropertyValue;
|
||||
|
||||
switch (pv.type()) {
|
||||
case PropertyValue::Type::Bool:
|
||||
return Value(pv.ValueBool());
|
||||
case PropertyValue::Type::Double:
|
||||
return Value(pv.ValueDouble());
|
||||
case PropertyValue::Type::Int:
|
||||
return Value(pv.ValueInt());
|
||||
case PropertyValue::Type::List: {
|
||||
std::vector<Value> list(pv.ValueList().size());
|
||||
for (const auto &elem : pv.ValueList()) {
|
||||
list.emplace_back(ToValue(elem));
|
||||
}
|
||||
|
||||
return Value(list);
|
||||
}
|
||||
case PropertyValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &[key, val] : pv.ValueMap()) {
|
||||
// maybe use std::make_pair once the && issue is resolved.
|
||||
map.emplace(key, ToValue(val));
|
||||
}
|
||||
|
||||
return Value(map);
|
||||
}
|
||||
case PropertyValue::Type::Null:
|
||||
return Value{};
|
||||
case PropertyValue::Type::String:
|
||||
return Value(pv.ValueString());
|
||||
case PropertyValue::Type::TemporalData: {
|
||||
// TBD -> we need to specify this in the messages, not a priority.
|
||||
MG_ASSERT(false, "Temporal datatypes are not yet implemented on Value!");
|
||||
return Value{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3::PropertyValue>> ConvertPropertyMap(
|
||||
std::vector<std::pair<PropertyId, Value>> &&properties) {
|
||||
std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3::PropertyValue>> ret;
|
||||
ret.reserve(properties.size());
|
||||
|
||||
for (auto &[key, value] : properties) {
|
||||
ret.emplace_back(std::make_pair(key, ToPropertyValue(std::move(value))));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<memgraph::storage::v3::PropertyValue> ConvertPropertyVector(std::vector<Value> &&vec) {
|
||||
std::vector<memgraph::storage::v3::PropertyValue> ret;
|
||||
ret.reserve(vec.size());
|
||||
|
||||
for (auto &elem : vec) {
|
||||
ret.push_back(ToPropertyValue(std::move(elem)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Value> ConvertValueVector(const std::vector<memgraph::storage::v3::PropertyValue> &vec) {
|
||||
std::vector<Value> ret;
|
||||
ret.reserve(vec.size());
|
||||
|
||||
for (const auto &elem : vec) {
|
||||
ret.push_back(ToValue(elem));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::map<PropertyId, Value>> CollectPropertiesFromAccessor(
|
||||
const memgraph::storage::v3::VertexAccessor &acc, const std::vector<memgraph::storage::v3::PropertyId> &props,
|
||||
memgraph::storage::v3::View view) {
|
||||
std::map<PropertyId, Value> ret;
|
||||
|
||||
for (const auto &prop : props) {
|
||||
auto result = acc.GetProperty(prop, view);
|
||||
if (result.HasError()) {
|
||||
spdlog::debug("Encountered an Error while trying to get a vertex property.");
|
||||
continue;
|
||||
}
|
||||
auto &value = result.GetValue();
|
||||
if (value.IsNull()) {
|
||||
spdlog::debug("The specified property does not exist but it should");
|
||||
continue;
|
||||
}
|
||||
ret.emplace(prop, ToValue(value));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::map<PropertyId, Value>> CollectAllPropertiesFromAccessor(
|
||||
const memgraph::storage::v3::VertexAccessor &acc, memgraph::storage::v3::View view) {
|
||||
std::map<PropertyId, Value> ret;
|
||||
auto iter = acc.Properties(view);
|
||||
if (iter.HasError()) {
|
||||
spdlog::debug("Encountered an error while trying to get vertex properties.");
|
||||
}
|
||||
|
||||
for (const auto &[prop_key, prop_val] : iter.GetValue()) {
|
||||
ret.emplace(prop_key, ToValue(prop_val));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Value ConstructValueVertex(const memgraph::storage::v3::VertexAccessor &acc, memgraph::storage::v3::View view) {
|
||||
// Get the vertex id
|
||||
auto prim_label = acc.PrimaryLabel(view).GetValue();
|
||||
Label value_label{.id = prim_label};
|
||||
|
||||
auto prim_key = ConvertValueVector(acc.PrimaryKey(view).GetValue());
|
||||
VertexId vertex_id = std::make_pair(value_label, prim_key);
|
||||
|
||||
// Get the labels
|
||||
auto vertex_labels = acc.Labels(view).GetValue();
|
||||
std::vector<Label> value_labels;
|
||||
for (const auto &label : vertex_labels) {
|
||||
Label l = {.id = label};
|
||||
value_labels.push_back(l);
|
||||
}
|
||||
|
||||
return Value({.id = vertex_id, .labels = value_labels});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
|
||||
auto acc = shard_->Access(req.transaction_id);
|
||||
|
||||
// Workaround untill we have access to CreateVertexAndValidate()
|
||||
// with the new signature that does not require the primary label.
|
||||
const auto prim_label = acc.GetPrimaryLabel();
|
||||
|
||||
bool action_successful = true;
|
||||
|
||||
for (auto &new_vertex : req.new_vertices) {
|
||||
/// TODO(gvolfing) Remove this. In the new implementation each shard
|
||||
/// should have a predetermined primary label, so there is no point in
|
||||
/// specifying it in the accessor functions. Their signature will
|
||||
/// change.
|
||||
/// TODO(gvolfing) Consider other methods than converting. Change either
|
||||
/// the way that the property map is stored in the messages, or the
|
||||
/// signature of CreateVertexAndValidate.
|
||||
auto converted_property_map = ConvertPropertyMap(std::move(new_vertex.properties));
|
||||
|
||||
// TODO(gvolfing) make sure if this conversion is actually needed.
|
||||
std::vector<memgraph::storage::v3::LabelId> converted_label_ids;
|
||||
converted_label_ids.reserve(new_vertex.label_ids.size());
|
||||
for (const auto &label_id : new_vertex.label_ids) {
|
||||
converted_label_ids.emplace_back(label_id.id);
|
||||
}
|
||||
|
||||
auto result_schema =
|
||||
acc.CreateVertexAndValidate(prim_label, converted_label_ids,
|
||||
ConvertPropertyVector(std::move(new_vertex.primary_key)), converted_property_map);
|
||||
|
||||
if (result_schema.HasError()) {
|
||||
auto &error = result_schema.GetError();
|
||||
|
||||
std::visit(
|
||||
[]<typename T>(T &&) {
|
||||
using ErrorType = std::remove_cvref_t<T>;
|
||||
if constexpr (std::is_same_v<ErrorType, SchemaViolation>) {
|
||||
spdlog::debug("Creating vertex failed with error: SchemaViolation");
|
||||
} else if constexpr (std::is_same_v<ErrorType, Error>) {
|
||||
spdlog::debug("Creating vertex failed with error: Error");
|
||||
} else {
|
||||
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
|
||||
}
|
||||
},
|
||||
error);
|
||||
|
||||
action_successful = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return msgs::CreateVerticesResponse{action_successful};
|
||||
}
|
||||
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
|
||||
bool action_successful = true;
|
||||
auto acc = shard_->Access(req.transaction_id);
|
||||
|
||||
for (auto &propval : req.primary_keys) {
|
||||
if (!action_successful) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto vertex_acc = acc.FindVertex(ConvertPropertyVector(std::move(propval)), View::OLD);
|
||||
|
||||
if (!vertex_acc) {
|
||||
spdlog::debug("Error while trying to delete vertex. Vertex to delete does not exist. Transaction id: {}",
|
||||
req.transaction_id.logical_id);
|
||||
action_successful = false;
|
||||
} else {
|
||||
// TODO(gvolfing)
|
||||
// Since we will not have different kinds of deletion types in one transaction,
|
||||
// we dont have to enter the switch statement on every iteration. Optimize this.
|
||||
switch (req.deletion_type) {
|
||||
case msgs::DeleteVerticesRequest::DeletionType::DELETE: {
|
||||
auto result = acc.DeleteVertex(&vertex_acc.value());
|
||||
if (result.HasError() || !(result.GetValue().has_value())) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Error while trying to delete vertex. Transaction id: {}", req.transaction_id.logical_id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case msgs::DeleteVerticesRequest::DeletionType::DETACH_DELETE: {
|
||||
auto result = acc.DetachDeleteVertex(&vertex_acc.value());
|
||||
if (result.HasError() || !(result.GetValue().has_value())) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Error while trying to detach and delete vertex. Transaction id: {}",
|
||||
req.transaction_id.logical_id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return msgs::DeleteVerticesResponse{action_successful};
|
||||
}
|
||||
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
|
||||
auto acc = shard_->Access(req.transaction_id);
|
||||
bool action_successful = true;
|
||||
|
||||
for (auto &edge : req.edges) {
|
||||
auto vertex_acc_from_primary_key = edge.src.second;
|
||||
auto vertex_from_acc = acc.FindVertex(ConvertPropertyVector(std::move(vertex_acc_from_primary_key)), View::OLD);
|
||||
|
||||
auto vertex_acc_to_primary_key = edge.dst.second;
|
||||
auto vertex_to_acc = acc.FindVertex(ConvertPropertyVector(std::move(vertex_acc_to_primary_key)), View::OLD);
|
||||
|
||||
if (!vertex_from_acc || !vertex_to_acc) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Error while trying to insert edge, vertex does not exist. Transaction id: {}",
|
||||
req.transaction_id.logical_id);
|
||||
break;
|
||||
}
|
||||
|
||||
auto from_vertex_id = VertexId(edge.src.first.id, ConvertPropertyVector(std::move(edge.src.second)));
|
||||
auto to_vertex_id = VertexId(edge.dst.first.id, ConvertPropertyVector(std::move(edge.dst.second)));
|
||||
auto edge_acc =
|
||||
acc.CreateEdge(from_vertex_id, to_vertex_id, EdgeTypeId::FromUint(edge.type.id), Gid::FromUint(edge.id.gid));
|
||||
|
||||
if (edge_acc.HasError()) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Creating edge was not successful. Transaction id: {}", req.transaction_id.logical_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return msgs::CreateEdgesResponse{action_successful};
|
||||
}
|
||||
|
||||
msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
|
||||
auto acc = shard_->Access(req.transaction_id);
|
||||
bool action_successful = true;
|
||||
|
||||
std::vector<msgs::ScanResultRow> results;
|
||||
std::optional<msgs::VertexId> next_start_id;
|
||||
|
||||
const auto view = View(req.storage_view);
|
||||
auto vertex_iterable = acc.Vertices(view);
|
||||
bool did_reach_starting_point = false;
|
||||
uint64_t sample_counter = 0;
|
||||
|
||||
for (auto it = vertex_iterable.begin(); it != vertex_iterable.end(); ++it) {
|
||||
const auto &vertex = *it;
|
||||
|
||||
if (ConvertPropertyVector(std::move(req.start_id.second)) == vertex.PrimaryKey(View(req.storage_view)).GetValue()) {
|
||||
did_reach_starting_point = true;
|
||||
}
|
||||
|
||||
if (did_reach_starting_point) {
|
||||
std::optional<std::map<PropertyId, Value>> found_props;
|
||||
|
||||
if (req.props_to_return) {
|
||||
found_props = CollectPropertiesFromAccessor(vertex, req.props_to_return.value(), view);
|
||||
} else {
|
||||
found_props = CollectAllPropertiesFromAccessor(vertex, view);
|
||||
}
|
||||
|
||||
if (!found_props) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.emplace_back(
|
||||
msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view), .props = found_props.value()});
|
||||
|
||||
++sample_counter;
|
||||
if (sample_counter == req.batch_limit) {
|
||||
// Reached the maximum specified batch size.
|
||||
// Get the next element before exiting.
|
||||
const auto &next_vertex = *(++it);
|
||||
next_start_id = ConstructValueVertex(next_vertex, view).vertex_v.id;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msgs::ScanVerticesResponse resp{};
|
||||
resp.success = action_successful;
|
||||
|
||||
if (action_successful) {
|
||||
resp.next_start_id = next_start_id;
|
||||
resp.results = std::move(results);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CommitRequest &&req) {
|
||||
shard_->Access(req.transaction_id).Commit(req.commit_timestamp);
|
||||
return msgs::CommitResponse{true};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateVerticesRequest && /*req*/) {
|
||||
return msgs::UpdateVerticesResponse{};
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteEdgesRequest && /*req*/) { return msgs::DeleteEdgesResponse{}; }
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateEdgesRequest && /*req*/) { return msgs::UpdateEdgesResponse{}; }
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest && /*req*/) { return msgs::ExpandOneResponse{}; }
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest && /*req*/) {
|
||||
return msgs::GetPropertiesResponse{};
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
59
src/storage/v3/shard_rsm.hpp
Normal file
59
src/storage/v3/shard_rsm.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
// 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 <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
template <typename>
|
||||
constexpr auto kAlwaysFalse = false;
|
||||
|
||||
class ShardRsm {
|
||||
std::unique_ptr<Shard> shard_;
|
||||
|
||||
msgs::ReadResponses HandleRead(msgs::ExpandOneRequest &&req);
|
||||
msgs::ReadResponses HandleRead(msgs::GetPropertiesRequest &&req);
|
||||
msgs::ReadResponses HandleRead(msgs::ScanVerticesRequest &&req);
|
||||
|
||||
msgs::WriteResponses ApplyWrite(msgs::CreateVerticesRequest &&req);
|
||||
msgs::WriteResponses ApplyWrite(msgs::DeleteVerticesRequest &&req);
|
||||
msgs::WriteResponses ApplyWrite(msgs::UpdateVerticesRequest &&req);
|
||||
|
||||
msgs::WriteResponses ApplyWrite(msgs::CreateEdgesRequest &&req);
|
||||
msgs::WriteResponses ApplyWrite(msgs::DeleteEdgesRequest &&req);
|
||||
msgs::WriteResponses ApplyWrite(msgs::UpdateEdgesRequest &&req);
|
||||
|
||||
msgs::WriteResponses ApplyWrite(msgs::CommitRequest &&req);
|
||||
|
||||
public:
|
||||
explicit ShardRsm(std::unique_ptr<Shard> &&shard) : shard_(std::move(shard)){};
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::ReadResponses Read(msgs::ReadRequests requests) {
|
||||
return std::visit([&](auto &&request) mutable { return HandleRead(std::forward<decltype(request)>(request)); },
|
||||
std::move(requests));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
msgs::WriteResponses Apply(msgs::WriteRequests requests) {
|
||||
return std::visit([&](auto &&request) mutable { return ApplyWrite(std::forward<decltype(request)>(request)); },
|
||||
std::move(requests));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
File diff suppressed because it is too large
Load Diff
@ -11,622 +11,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "storage/v3/commit_log.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/constraints.hpp"
|
||||
#include "storage/v3/durability/metadata.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
|
||||
/// REPLICATION ///
|
||||
#include "rpc/server.hpp"
|
||||
#include "storage/v3/replication/config.hpp"
|
||||
#include "storage/v3/replication/enums.hpp"
|
||||
#include "storage/v3/replication/rpc.hpp"
|
||||
#include "storage/v3/replication/serialization.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// The storage is based on this paper:
|
||||
// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf
|
||||
// The paper implements a fully serializable storage, in our implementation we
|
||||
// only implement snapshot isolation for transactions.
|
||||
|
||||
/// Iterable for iterating through all vertices of a Storage.
|
||||
///
|
||||
/// An instance of this will be usually be wrapped inside VerticesIterable for
|
||||
/// generic, public use.
|
||||
class AllVerticesIterable final {
|
||||
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
||||
Transaction *transaction_;
|
||||
View view_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
std::optional<VertexAccessor> vertex_;
|
||||
|
||||
class Storage {
|
||||
public:
|
||||
class Iterator final {
|
||||
AllVerticesIterable *self_;
|
||||
utils::SkipList<Vertex>::Iterator it_;
|
||||
|
||||
public:
|
||||
Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it);
|
||||
|
||||
VertexAccessor operator*() const;
|
||||
|
||||
Iterator &operator++();
|
||||
|
||||
bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
AllVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||
Indices *indices, Constraints *constraints, Config::Items config,
|
||||
SchemaValidator *schema_validator)
|
||||
: vertices_accessor_(std::move(vertices_accessor)),
|
||||
transaction_(transaction),
|
||||
view_(view),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
schema_validator_(schema_validator) {}
|
||||
|
||||
Iterator begin() { return {this, vertices_accessor_.begin()}; }
|
||||
Iterator end() { return {this, vertices_accessor_.end()}; }
|
||||
};
|
||||
|
||||
/// Generic access to different kinds of vertex iterations.
|
||||
///
|
||||
/// This class should be the primary type used by the client code to iterate
|
||||
/// over vertices inside a Storage instance.
|
||||
class VerticesIterable final {
|
||||
enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
||||
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable all_vertices_;
|
||||
LabelIndex::Iterable vertices_by_label_;
|
||||
LabelPropertyIndex::Iterable vertices_by_label_property_;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit VerticesIterable(AllVerticesIterable);
|
||||
explicit VerticesIterable(LabelIndex::Iterable);
|
||||
explicit VerticesIterable(LabelPropertyIndex::Iterable);
|
||||
|
||||
VerticesIterable(const VerticesIterable &) = delete;
|
||||
VerticesIterable &operator=(const VerticesIterable &) = delete;
|
||||
|
||||
VerticesIterable(VerticesIterable &&) noexcept;
|
||||
VerticesIterable &operator=(VerticesIterable &&) noexcept;
|
||||
|
||||
~VerticesIterable();
|
||||
|
||||
class Iterator final {
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable::Iterator all_it_;
|
||||
LabelIndex::Iterable::Iterator by_label_it_;
|
||||
LabelPropertyIndex::Iterable::Iterator by_label_property_it_;
|
||||
};
|
||||
|
||||
void Destroy() noexcept;
|
||||
|
||||
public:
|
||||
explicit Iterator(AllVerticesIterable::Iterator);
|
||||
explicit Iterator(LabelIndex::Iterable::Iterator);
|
||||
explicit Iterator(LabelPropertyIndex::Iterable::Iterator);
|
||||
|
||||
Iterator(const Iterator &);
|
||||
Iterator &operator=(const Iterator &);
|
||||
|
||||
Iterator(Iterator &&) noexcept;
|
||||
Iterator &operator=(Iterator &&) noexcept;
|
||||
|
||||
~Iterator();
|
||||
|
||||
VertexAccessor operator*() const;
|
||||
|
||||
Iterator &operator++();
|
||||
|
||||
bool operator==(const Iterator &other) const;
|
||||
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
Iterator begin();
|
||||
Iterator end();
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing indices in the storage.
|
||||
struct IndicesInfo {
|
||||
std::vector<LabelId> label;
|
||||
std::vector<std::pair<LabelId, PropertyId>> label_property;
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing constraints in the
|
||||
/// storage.
|
||||
struct ConstraintsInfo {
|
||||
std::vector<std::pair<LabelId, PropertyId>> existence;
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing schemas in the storage
|
||||
struct SchemasInfo {
|
||||
Schemas::SchemasList schemas;
|
||||
};
|
||||
|
||||
/// Structure used to return information about the storage.
|
||||
struct StorageInfo {
|
||||
uint64_t vertex_count;
|
||||
uint64_t edge_count;
|
||||
double average_degree;
|
||||
uint64_t memory_usage;
|
||||
uint64_t disk_usage;
|
||||
};
|
||||
|
||||
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
|
||||
|
||||
class Storage final {
|
||||
public:
|
||||
/// @throw std::system_error
|
||||
/// @throw std::bad_alloc
|
||||
explicit Storage(Config config = Config());
|
||||
|
||||
~Storage();
|
||||
|
||||
class Accessor final {
|
||||
private:
|
||||
friend class Storage;
|
||||
|
||||
explicit Accessor(Storage *storage, IsolationLevel isolation_level);
|
||||
|
||||
public:
|
||||
Accessor(const Accessor &) = delete;
|
||||
Accessor &operator=(const Accessor &) = delete;
|
||||
Accessor &operator=(Accessor &&other) = delete;
|
||||
|
||||
// NOTE: After the accessor is moved, all objects derived from it (accessors
|
||||
// and iterators) are *invalid*. You have to get all derived objects again.
|
||||
Accessor(Accessor &&other) noexcept;
|
||||
|
||||
~Accessor();
|
||||
|
||||
VertexAccessor CreateVertex();
|
||||
|
||||
VertexAccessor CreateVertex(Gid gid);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<VertexAccessor> CreateVertexAndValidate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties);
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
||||
|
||||
VerticesIterable Vertices(View view) {
|
||||
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||
&storage_->indices_, &storage_->constraints_, storage_->config_.items,
|
||||
&storage_->schema_validator_));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(LabelId label, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view);
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view);
|
||||
|
||||
/// Return approximate number of all vertices in the database.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount() const { return static_cast<int64_t>(storage_->vertices_.size()); }
|
||||
|
||||
/// Return approximate number of vertices with the given label.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label) const {
|
||||
return storage_->indices_.label_index.ApproximateVertexCount(label);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and property.
|
||||
/// Note that this is always an over-estimate and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and the given
|
||||
/// value for the given property. Note that this is always an over-estimate
|
||||
/// and never an under-estimate.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const {
|
||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value);
|
||||
}
|
||||
|
||||
/// Return approximate number of vertices with the given label and value for
|
||||
/// the given property in the range defined by provided upper and lower
|
||||
/// bounds.
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper) const {
|
||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
|
||||
}
|
||||
|
||||
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex);
|
||||
|
||||
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
||||
VertexAccessor *vertex);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type);
|
||||
|
||||
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
||||
|
||||
const std::string &LabelToName(LabelId label) const;
|
||||
const std::string &PropertyToName(PropertyId property) const;
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const {
|
||||
return storage_->indices_.label_property_index.IndexExists(label, property);
|
||||
}
|
||||
|
||||
IndicesInfo ListAllIndices() const {
|
||||
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
|
||||
}
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const {
|
||||
return {ListExistenceConstraints(storage_->constraints_),
|
||||
storage_->constraints_.unique_constraints.ListConstraints()};
|
||||
}
|
||||
|
||||
const SchemaValidator &GetSchemaValidator() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const { return {storage_->schemas_.ListSchemas()}; }
|
||||
|
||||
void AdvanceCommand();
|
||||
|
||||
/// Commit returns `ConstraintViolation` if the changes made by this
|
||||
/// transaction violate an existence or unique constraint. In that case the
|
||||
/// transaction is automatically aborted. Otherwise, void is returned.
|
||||
/// @throw std::bad_alloc
|
||||
utils::BasicResult<ConstraintViolation, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void Abort();
|
||||
|
||||
void FinalizeTransaction();
|
||||
|
||||
private:
|
||||
/// @throw std::bad_alloc
|
||||
VertexAccessor CreateVertex(Gid gid, LabelId primary_label);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid);
|
||||
|
||||
Storage *storage_;
|
||||
std::shared_lock<utils::RWLock> storage_guard_;
|
||||
Transaction transaction_;
|
||||
std::optional<uint64_t> commit_timestamp_;
|
||||
bool is_transaction_active_;
|
||||
Config::Items config_;
|
||||
};
|
||||
|
||||
Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) {
|
||||
return Accessor{this, override_isolation_level.value_or(isolation_level_)};
|
||||
}
|
||||
|
||||
const std::string &LabelToName(LabelId label) const;
|
||||
const std::string &PropertyToName(PropertyId property) const;
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
bool DropIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
bool DropIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
IndicesInfo ListAllIndices() const;
|
||||
|
||||
/// Creates an existence constraint. Returns true if the constraint was
|
||||
/// successfully added, false if it already exists and a `ConstraintViolation`
|
||||
/// if there is an existing vertex violating the constraint.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
|
||||
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// Removes an existence constraint. Returns true if the constraint was
|
||||
/// removed, and false if it doesn't exist.
|
||||
bool DropExistenceConstraint(LabelId label, PropertyId property,
|
||||
std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// Creates a unique constraint. In the case of two vertices violating the
|
||||
/// constraint, it returns `ConstraintViolation`. Otherwise returns a
|
||||
/// `UniqueConstraints::CreationStatus` enum with the following possibilities:
|
||||
/// * `SUCCESS` if the constraint was successfully created,
|
||||
/// * `ALREADY_EXISTS` if the constraint already existed,
|
||||
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
||||
// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
|
||||
// limit of maximum number of properties.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
|
||||
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
/// Removes a unique constraint. Returns `UniqueConstraints::DeletionStatus`
|
||||
/// enum with the following possibilities:
|
||||
/// * `SUCCESS` if constraint was successfully removed,
|
||||
/// * `NOT_FOUND` if the specified constraint was not found,
|
||||
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
||||
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
|
||||
// limit of maximum number of properties.
|
||||
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
|
||||
std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const;
|
||||
|
||||
const Schemas::Schema *GetSchema(LabelId primary_label) const;
|
||||
|
||||
bool CreateSchema(LabelId primary_label, const std::vector<SchemaProperty> &schemas_types);
|
||||
|
||||
bool DropSchema(LabelId primary_label);
|
||||
|
||||
StorageInfo GetInfo() const;
|
||||
|
||||
bool LockPath();
|
||||
bool UnlockPath();
|
||||
|
||||
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {});
|
||||
|
||||
bool SetMainReplicationRole();
|
||||
|
||||
enum class RegisterReplicaError : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
CONNECTION_FAILED,
|
||||
COULD_NOT_BE_PERSISTED
|
||||
};
|
||||
|
||||
/// @pre The instance should have a MAIN role
|
||||
/// @pre Timeout can only be set for SYNC replication
|
||||
utils::BasicResult<RegisterReplicaError, void> RegisterReplica(
|
||||
std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode,
|
||||
const replication::ReplicationClientConfig &config = {});
|
||||
/// @pre The instance should have a MAIN role
|
||||
bool UnregisterReplica(std::string_view name);
|
||||
|
||||
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name);
|
||||
|
||||
ReplicationRole GetReplicationRole() const;
|
||||
|
||||
struct ReplicaInfo {
|
||||
std::string name;
|
||||
replication::ReplicationMode mode;
|
||||
std::optional<double> timeout;
|
||||
io::network::Endpoint endpoint;
|
||||
replication::ReplicaState state;
|
||||
};
|
||||
|
||||
std::vector<ReplicaInfo> ReplicasInfo();
|
||||
|
||||
void FreeMemory();
|
||||
|
||||
void SetIsolationLevel(IsolationLevel isolation_level);
|
||||
|
||||
enum class CreateSnapshotError : uint8_t { DisabledForReplica };
|
||||
|
||||
utils::BasicResult<CreateSnapshotError> CreateSnapshot();
|
||||
explicit Storage(Config config);
|
||||
// Interface toward shard manipulation
|
||||
// Shard handler -> will use rsm client
|
||||
|
||||
private:
|
||||
Transaction CreateTransaction(IsolationLevel isolation_level);
|
||||
|
||||
/// The force parameter determines the behaviour of the garbage collector.
|
||||
/// If it's set to true, it will behave as a global operation, i.e. it can't
|
||||
/// be part of a transaction, and no other transaction can be active at the same time.
|
||||
/// This allows it to delete immediately vertices without worrying that some other
|
||||
/// transaction is possibly using it. If there are active transactions when this method
|
||||
/// is called with force set to true, it will fallback to the same method with the force
|
||||
/// set to false.
|
||||
/// If it's set to false, it will execute in parallel with other transactions, ensuring
|
||||
/// that no object in use can be deleted.
|
||||
/// @throw std::system_error
|
||||
/// @throw std::bad_alloc
|
||||
template <bool force>
|
||||
void CollectGarbage();
|
||||
|
||||
bool InitializeWalFile();
|
||||
void FinalizeWalFile();
|
||||
|
||||
void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp);
|
||||
void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t final_commit_timestamp);
|
||||
|
||||
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
// Main storage lock.
|
||||
//
|
||||
// Accessors take a shared lock when starting, so it is possible to block
|
||||
// creation of new accessors by taking a unique lock. This is used when doing
|
||||
// operations on storage that affect the global state, for example index
|
||||
// creation.
|
||||
mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE};
|
||||
|
||||
// Main object storage
|
||||
utils::SkipList<Vertex> vertices_;
|
||||
utils::SkipList<Edge> edges_;
|
||||
std::atomic<uint64_t> vertex_id_{0};
|
||||
std::atomic<uint64_t> edge_id_{0};
|
||||
// Even though the edge count is already kept in the `edges_` SkipList, the
|
||||
// list is used only when properties are enabled for edges. Because of that we
|
||||
// keep a separate count of edges that is always updated.
|
||||
std::atomic<uint64_t> edge_count_{0};
|
||||
|
||||
NameIdMapper name_id_mapper_;
|
||||
|
||||
SchemaValidator schema_validator_;
|
||||
Constraints constraints_;
|
||||
Indices indices_;
|
||||
Schemas schemas_;
|
||||
|
||||
// Transaction engine
|
||||
utils::SpinLock engine_lock_;
|
||||
uint64_t timestamp_{kTimestampInitialId};
|
||||
uint64_t transaction_id_{kTransactionInitialId};
|
||||
// TODO: This isn't really a commit log, it doesn't even care if a
|
||||
// transaction commited or aborted. We could probably combine this with
|
||||
// `timestamp_` in a sensible unit, something like TransactionClock or
|
||||
// whatever.
|
||||
std::optional<CommitLog> commit_log_;
|
||||
|
||||
utils::Synchronized<std::list<Transaction>, utils::SpinLock> committed_transactions_;
|
||||
IsolationLevel isolation_level_;
|
||||
|
||||
std::vector<Shard> shards_;
|
||||
boost::asio::thread_pool shard_handlers_;
|
||||
Config config_;
|
||||
utils::Scheduler gc_runner_;
|
||||
std::mutex gc_lock_;
|
||||
|
||||
// Undo buffers that were unlinked and now are waiting to be freed.
|
||||
utils::Synchronized<std::list<std::pair<uint64_t, std::list<Delta>>>, utils::SpinLock> garbage_undo_buffers_;
|
||||
|
||||
// Vertices that are logically deleted but still have to be removed from
|
||||
// indices before removing them from the main storage.
|
||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_;
|
||||
|
||||
// Vertices that are logically deleted and removed from indices and now wait
|
||||
// to be removed from the main storage.
|
||||
std::list<std::pair<uint64_t, Gid>> garbage_vertices_;
|
||||
|
||||
// Edges that are logically deleted and wait to be removed from the main
|
||||
// storage.
|
||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_;
|
||||
|
||||
// Durability
|
||||
std::filesystem::path snapshot_directory_;
|
||||
std::filesystem::path wal_directory_;
|
||||
std::filesystem::path lock_file_path_;
|
||||
utils::OutputFile lock_file_handle_;
|
||||
|
||||
utils::Scheduler snapshot_runner_;
|
||||
utils::SpinLock snapshot_lock_;
|
||||
|
||||
// UUID used to distinguish snapshots and to link snapshots to WALs
|
||||
std::string uuid_;
|
||||
// Sequence number used to keep track of the chain of WALs.
|
||||
uint64_t wal_seq_num_{0};
|
||||
|
||||
// UUID to distinguish different main instance runs for replication process
|
||||
// on SAME storage.
|
||||
// Multiple instances can have same storage UUID and be MAIN at the same time.
|
||||
// We cannot compare commit timestamps of those instances if one of them
|
||||
// becomes the replica of the other so we use epoch_id_ as additional
|
||||
// discriminating property.
|
||||
// Example of this:
|
||||
// We have 2 instances of the same storage, S1 and S2.
|
||||
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
|
||||
// At the moment when S1 commited a transaction with timestamp 20, and S2
|
||||
// a different transaction with timestamp 15, we change S2's role to REPLICA
|
||||
// and register it on S1.
|
||||
// Without using the epoch_id, we don't know that S1 and S2 have completely
|
||||
// different transactions, we think that the S2 is behind only by 5 commits.
|
||||
std::string epoch_id_;
|
||||
// History of the previous epoch ids.
|
||||
// Each value consists of the epoch id along the last commit belonging to that
|
||||
// epoch.
|
||||
std::deque<std::pair<std::string, uint64_t>> epoch_history_;
|
||||
|
||||
std::optional<durability::WalFile> wal_file_;
|
||||
uint64_t wal_unsynced_transactions_{0};
|
||||
|
||||
utils::FileRetainer file_retainer_;
|
||||
|
||||
// Global locker that is used for clients file locking
|
||||
utils::FileRetainer::FileLocker global_locker_;
|
||||
|
||||
// Last commited timestamp
|
||||
std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId};
|
||||
|
||||
class ReplicationServer;
|
||||
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
|
||||
|
||||
class ReplicationClient;
|
||||
// We create ReplicationClient using unique_ptr so we can move
|
||||
// newly created client into the vector.
|
||||
// We cannot move the client directly because it contains ThreadPool
|
||||
// which cannot be moved. Also, the move is necessary because
|
||||
// we don't want to create the client directly inside the vector
|
||||
// because that would require the lock on the list putting all
|
||||
// commits (they iterate list of clients) to halt.
|
||||
// This way we can initialize client in main thread which means
|
||||
// that we can immediately notify the user if the initialization
|
||||
// failed.
|
||||
using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>;
|
||||
ReplicationClientList replication_clients_;
|
||||
|
||||
std::atomic<ReplicationRole> replication_role_{ReplicationRole::MAIN};
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -11,13 +11,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "storage/v3/delta.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
@ -27,24 +25,27 @@
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
const uint64_t kTimestampInitialId = 0;
|
||||
const uint64_t kTransactionInitialId = 1ULL << 63U;
|
||||
struct CommitInfo {
|
||||
bool is_locally_committed{false};
|
||||
coordinator::Hlc start_or_commit_timestamp;
|
||||
};
|
||||
|
||||
struct Transaction {
|
||||
Transaction(uint64_t transaction_id, uint64_t start_timestamp, IsolationLevel isolation_level)
|
||||
: transaction_id(transaction_id),
|
||||
start_timestamp(start_timestamp),
|
||||
Transaction(coordinator::Hlc start_timestamp, IsolationLevel isolation_level)
|
||||
: start_timestamp(start_timestamp),
|
||||
commit_info(std::make_unique<CommitInfo>(CommitInfo{false, {start_timestamp}})),
|
||||
command_id(0),
|
||||
must_abort(false),
|
||||
is_aborted(false),
|
||||
isolation_level(isolation_level) {}
|
||||
|
||||
Transaction(Transaction &&other) noexcept
|
||||
: transaction_id(other.transaction_id),
|
||||
start_timestamp(other.start_timestamp),
|
||||
commit_timestamp(std::move(other.commit_timestamp)),
|
||||
: start_timestamp(other.start_timestamp),
|
||||
commit_info(std::move(other.commit_info)),
|
||||
command_id(other.command_id),
|
||||
deltas(std::move(other.deltas)),
|
||||
must_abort(other.must_abort),
|
||||
is_aborted(other.is_aborted),
|
||||
isolation_level(other.isolation_level) {}
|
||||
|
||||
Transaction(const Transaction &) = delete;
|
||||
@ -53,32 +54,23 @@ struct Transaction {
|
||||
|
||||
~Transaction() {}
|
||||
|
||||
/// @throw std::bad_alloc if failed to create the `commit_timestamp`
|
||||
void EnsureCommitTimestampExists() {
|
||||
if (commit_timestamp != nullptr) return;
|
||||
commit_timestamp = std::make_unique<std::atomic<uint64_t>>(transaction_id);
|
||||
}
|
||||
|
||||
uint64_t transaction_id;
|
||||
uint64_t start_timestamp;
|
||||
// The `Transaction` object is stack allocated, but the `commit_timestamp`
|
||||
// must be heap allocated because `Delta`s have a pointer to it, and that
|
||||
// pointer must stay valid after the `Transaction` is moved into
|
||||
// `commited_transactions_` list for GC.
|
||||
std::unique_ptr<std::atomic<uint64_t>> commit_timestamp;
|
||||
coordinator::Hlc start_timestamp;
|
||||
std::unique_ptr<CommitInfo> commit_info;
|
||||
uint64_t command_id;
|
||||
std::list<Delta> deltas;
|
||||
bool must_abort;
|
||||
bool is_aborted;
|
||||
IsolationLevel isolation_level;
|
||||
};
|
||||
|
||||
// Relies on start timestamps are unique
|
||||
inline bool operator==(const Transaction &first, const Transaction &second) {
|
||||
return first.transaction_id == second.transaction_id;
|
||||
return first.start_timestamp == second.start_timestamp;
|
||||
}
|
||||
inline bool operator<(const Transaction &first, const Transaction &second) {
|
||||
return first.transaction_id < second.transaction_id;
|
||||
return first.start_timestamp < second.start_timestamp;
|
||||
}
|
||||
inline bool operator==(const Transaction &first, const uint64_t &second) { return first.transaction_id == second; }
|
||||
inline bool operator<(const Transaction &first, const uint64_t &second) { return first.transaction_id < second; }
|
||||
inline bool operator==(const Transaction &first, const uint64_t second) { return first.start_timestamp == second; }
|
||||
inline bool operator<(const Transaction &first, const uint64_t second) { return first.start_timestamp < second; }
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -13,53 +13,39 @@
|
||||
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/delta.hpp"
|
||||
#include "storage/v3/edge_ref.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/vertex_id.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct Vertex {
|
||||
Vertex(Gid gid, Delta *delta, LabelId primary_label)
|
||||
: gid(gid), primary_label{primary_label}, deleted(false), delta(delta) {
|
||||
using EdgeLink = std::tuple<EdgeTypeId, VertexId, EdgeRef>;
|
||||
|
||||
Vertex(Delta *delta, const std::vector<PropertyValue> &primary_properties) : keys{primary_properties}, delta{delta} {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
// TODO remove this when import replication is solved
|
||||
Vertex(Gid gid, LabelId primary_label) : gid(gid), primary_label{primary_label}, deleted(false) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
friend bool operator==(const Vertex &vertex, const PrimaryKey &primary_key) { return vertex.keys == primary_key; }
|
||||
|
||||
// TODO remove this when import csv is solved
|
||||
Vertex(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
KeyStore keys;
|
||||
|
||||
// TODO remove this when import replication is solved
|
||||
explicit Vertex(Gid gid) : gid(gid), deleted(false) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
Gid gid;
|
||||
|
||||
LabelId primary_label;
|
||||
std::vector<LabelId> labels;
|
||||
PropertyStore properties;
|
||||
std::vector<EdgeLink> in_edges;
|
||||
std::vector<EdgeLink> out_edges;
|
||||
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
|
||||
|
||||
mutable utils::SpinLock lock;
|
||||
bool deleted;
|
||||
bool deleted{false};
|
||||
// uint8_t PAD;
|
||||
// uint16_t PAD;
|
||||
|
||||
@ -68,13 +54,6 @@ struct Vertex {
|
||||
|
||||
static_assert(alignof(Vertex) >= 8, "The Vertex should be aligned to at least 8!");
|
||||
|
||||
inline bool operator==(const Vertex &first, const Vertex &second) { return first.gid == second.gid; }
|
||||
inline bool operator<(const Vertex &first, const Vertex &second) { return first.gid < second.gid; }
|
||||
inline bool operator==(const Vertex &first, const Gid &second) { return first.gid == second; }
|
||||
inline bool operator<(const Vertex &first, const Gid &second) { return first.gid < second; }
|
||||
|
||||
inline bool VertexHasLabel(const Vertex &vertex, const LabelId label) {
|
||||
return vertex.primary_label == label || utils::Contains(vertex.labels, label);
|
||||
}
|
||||
inline bool VertexHasLabel(const Vertex &vertex, const LabelId label) { return utils::Contains(vertex.labels, label); }
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -13,9 +13,11 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
@ -32,7 +34,6 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v
|
||||
bool deleted = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||
deleted = vertex->deleted;
|
||||
delta = vertex->delta;
|
||||
}
|
||||
@ -63,13 +64,13 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v
|
||||
} // namespace detail
|
||||
|
||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, View view) {
|
||||
Config::Items config, const VertexValidator &vertex_validator,
|
||||
View view) {
|
||||
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return VertexAccessor{vertex, transaction, indices, constraints, config, schema_validator};
|
||||
return VertexAccessor{vertex, transaction, indices, config, vertex_validator};
|
||||
}
|
||||
|
||||
bool VertexAccessor::IsVisible(View view) const {
|
||||
@ -79,7 +80,6 @@ bool VertexAccessor::IsVisible(View view) const {
|
||||
|
||||
Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
@ -97,11 +97,10 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||
}
|
||||
|
||||
ResultSchema<bool> VertexAccessor::AddLabelAndValidate(LabelId label) {
|
||||
if (const auto maybe_violation_error = vertex_validator_.ValidateAddLabel(label); maybe_violation_error) {
|
||||
if (const auto maybe_violation_error = vertex_validator_->ValidateAddLabel(label); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR};
|
||||
|
||||
@ -119,8 +118,6 @@ ResultSchema<bool> VertexAccessor::AddLabelAndValidate(LabelId label) {
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||
@ -136,10 +133,9 @@ Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
|
||||
}
|
||||
|
||||
ResultSchema<bool> VertexAccessor::RemoveLabelAndValidate(LabelId label) {
|
||||
if (const auto maybe_violation_error = vertex_validator_.ValidateRemoveLabel(label); maybe_violation_error) {
|
||||
if (const auto maybe_violation_error = vertex_validator_->ValidateRemoveLabel(label); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR};
|
||||
|
||||
@ -155,15 +151,16 @@ ResultSchema<bool> VertexAccessor::RemoveLabelAndValidate(LabelId label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::HasLabel(View view, LabelId label) const { return HasLabel(label, view); }
|
||||
|
||||
Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
bool has_label = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
has_label = VertexHasLabel(*vertex_, label);
|
||||
has_label = label == vertex_validator_->primary_label_ || VertexHasLabel(*vertex_, label);
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &has_label, label](const Delta &delta) {
|
||||
@ -204,46 +201,33 @@ Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
}
|
||||
|
||||
Result<LabelId> VertexAccessor::PrimaryLabel(const View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
delta = vertex_->delta;
|
||||
if (const auto result = CheckVertexExistence(view); result.HasError()) {
|
||||
return result.GetError();
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||
return vertex_->primary_label;
|
||||
|
||||
return vertex_validator_->primary_label_;
|
||||
}
|
||||
|
||||
Result<PrimaryKey> VertexAccessor::PrimaryKey(const View view) const {
|
||||
if (const auto result = CheckVertexExistence(view); result.HasError()) {
|
||||
return result.GetError();
|
||||
}
|
||||
return vertex_->keys.Keys();
|
||||
}
|
||||
|
||||
Result<VertexId> VertexAccessor::Id(View view) const {
|
||||
if (const auto result = CheckVertexExistence(view); result.HasError()) {
|
||||
return result.GetError();
|
||||
}
|
||||
return VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()};
|
||||
};
|
||||
|
||||
Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
std::vector<LabelId> labels;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
labels = vertex_->labels;
|
||||
delta = vertex_->delta;
|
||||
@ -288,7 +272,6 @@ Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
|
||||
|
||||
Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
@ -309,12 +292,48 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
|
||||
return std::move(current_value);
|
||||
}
|
||||
|
||||
Result<void> VertexAccessor::CheckVertexExistence(View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
deleted = vertex_->deleted;
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (!exists) {
|
||||
return Error::NONEXISTENT_OBJECT;
|
||||
}
|
||||
if (!for_deleted_ && deleted) {
|
||||
return Error::DELETED_OBJECT;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ResultSchema<PropertyValue> VertexAccessor::SetPropertyAndValidate(PropertyId property, const PropertyValue &value) {
|
||||
if (auto maybe_violation_error = vertex_validator_.ValidatePropertyUpdate(property); maybe_violation_error) {
|
||||
if (auto maybe_violation_error = vertex_validator_->ValidatePropertyUpdate(property); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) {
|
||||
return {Error::SERIALIZATION_ERROR};
|
||||
@ -340,8 +359,6 @@ ResultSchema<PropertyValue> VertexAccessor::SetPropertyAndValidate(PropertyId pr
|
||||
}
|
||||
|
||||
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||
@ -357,13 +374,16 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
Result<PropertyValue> VertexAccessor::GetProperty(View view, PropertyId property) const {
|
||||
return GetProperty(property, view).GetValue();
|
||||
}
|
||||
|
||||
Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
PropertyValue value;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
value = vertex_->properties.GetProperty(property);
|
||||
delta = vertex_->delta;
|
||||
@ -404,7 +424,6 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view
|
||||
std::map<PropertyId, PropertyValue> properties;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
properties = vertex_->properties.Properties();
|
||||
delta = vertex_->delta;
|
||||
@ -449,21 +468,21 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view
|
||||
}
|
||||
|
||||
Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||
const VertexAccessor *destination) const {
|
||||
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
||||
const VertexId *destination_id) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
|
||||
std::vector<Vertex::EdgeLink> in_edges;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
if (edge_types.empty() && !destination) {
|
||||
if (edge_types.empty() && nullptr == destination_id) {
|
||||
in_edges = vertex_->in_edges;
|
||||
} else {
|
||||
for (const auto &item : vertex_->in_edges) {
|
||||
const auto &[edge_type, from_vertex, edge] = item;
|
||||
if (destination && from_vertex != destination->vertex_) continue;
|
||||
if (nullptr != destination_id && from_vertex != *destination_id) {
|
||||
continue;
|
||||
};
|
||||
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
||||
continue;
|
||||
in_edges.push_back(item);
|
||||
@ -472,29 +491,27 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(
|
||||
transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, &destination](const Delta &delta) {
|
||||
transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, destination_id](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_IN_EDGE: {
|
||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||
if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) break;
|
||||
if (!edge_types.empty() &&
|
||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||
break;
|
||||
// Add the edge because we don't see the removal.
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex,
|
||||
delta.vertex_edge.edge};
|
||||
Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge};
|
||||
auto it = std::find(in_edges.begin(), in_edges.end(), link);
|
||||
MG_ASSERT(it == in_edges.end(), "Invalid database state!");
|
||||
in_edges.push_back(link);
|
||||
break;
|
||||
}
|
||||
case Delta::Action::REMOVE_IN_EDGE: {
|
||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||
if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) break;
|
||||
if (!edge_types.empty() &&
|
||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||
break;
|
||||
// Remove the label because we don't see the addition.
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex,
|
||||
delta.vertex_edge.edge};
|
||||
Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge};
|
||||
auto it = std::find(in_edges.begin(), in_edges.end(), link);
|
||||
MG_ASSERT(it != in_edges.end(), "Invalid database state!");
|
||||
std::swap(*it, *in_edges.rbegin());
|
||||
@ -520,31 +537,32 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (deleted) return Error::DELETED_OBJECT;
|
||||
std::vector<EdgeAccessor> ret;
|
||||
if (in_edges.empty()) {
|
||||
return ret;
|
||||
}
|
||||
ret.reserve(in_edges.size());
|
||||
const auto id = VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()};
|
||||
for (const auto &item : in_edges) {
|
||||
const auto &[edge_type, from_vertex, edge] = item;
|
||||
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_,
|
||||
*vertex_validator_.schema_validator);
|
||||
ret.emplace_back(edge, edge_type, from_vertex, id, transaction_, indices_, config_);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||
const VertexAccessor *destination) const {
|
||||
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
||||
const VertexId *destination_id) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
|
||||
std::vector<Vertex::EdgeLink> out_edges;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
if (edge_types.empty() && !destination) {
|
||||
if (edge_types.empty() && nullptr == destination_id) {
|
||||
out_edges = vertex_->out_edges;
|
||||
} else {
|
||||
for (const auto &item : vertex_->out_edges) {
|
||||
const auto &[edge_type, to_vertex, edge] = item;
|
||||
if (destination && to_vertex != destination->vertex_) continue;
|
||||
if (nullptr != destination_id && to_vertex != *destination_id) continue;
|
||||
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
||||
continue;
|
||||
out_edges.push_back(item);
|
||||
@ -553,29 +571,27 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std:
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(
|
||||
transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, &destination](const Delta &delta) {
|
||||
transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, destination_id](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_OUT_EDGE: {
|
||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||
if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) break;
|
||||
if (!edge_types.empty() &&
|
||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||
break;
|
||||
// Add the edge because we don't see the removal.
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex,
|
||||
delta.vertex_edge.edge};
|
||||
Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge};
|
||||
auto it = std::find(out_edges.begin(), out_edges.end(), link);
|
||||
MG_ASSERT(it == out_edges.end(), "Invalid database state!");
|
||||
out_edges.push_back(link);
|
||||
break;
|
||||
}
|
||||
case Delta::Action::REMOVE_OUT_EDGE: {
|
||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||
if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) break;
|
||||
if (!edge_types.empty() &&
|
||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||
break;
|
||||
// Remove the label because we don't see the addition.
|
||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex,
|
||||
delta.vertex_edge.edge};
|
||||
Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge};
|
||||
auto it = std::find(out_edges.begin(), out_edges.end(), link);
|
||||
MG_ASSERT(it != out_edges.end(), "Invalid database state!");
|
||||
std::swap(*it, *out_edges.rbegin());
|
||||
@ -601,13 +617,16 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std:
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (deleted) return Error::DELETED_OBJECT;
|
||||
std::vector<EdgeAccessor> ret;
|
||||
if (out_edges.empty()) {
|
||||
return ret;
|
||||
}
|
||||
ret.reserve(out_edges.size());
|
||||
const auto id = VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()};
|
||||
for (const auto &item : out_edges) {
|
||||
const auto &[edge_type, to_vertex, edge] = item;
|
||||
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_,
|
||||
*vertex_validator_.schema_validator);
|
||||
ret.emplace_back(edge, edge_type, id, to_vertex, transaction_, indices_, config_);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result<size_t> VertexAccessor::InDegree(View view) const {
|
||||
@ -616,7 +635,6 @@ Result<size_t> VertexAccessor::InDegree(View view) const {
|
||||
size_t degree = 0;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
degree = vertex_->in_edges.size();
|
||||
delta = vertex_->delta;
|
||||
@ -654,7 +672,6 @@ Result<size_t> VertexAccessor::OutDegree(View view) const {
|
||||
size_t degree = 0;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
degree = vertex_->out_edges.size();
|
||||
delta = vertex_->delta;
|
||||
@ -686,21 +703,4 @@ Result<size_t> VertexAccessor::OutDegree(View view) const {
|
||||
return degree;
|
||||
}
|
||||
|
||||
VertexAccessor::VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex)
|
||||
: schema_validator{&schema_validator}, vertex_{vertex} {}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidatePropertyUpdate(
|
||||
PropertyId property_id) const {
|
||||
MG_ASSERT(vertex_ != nullptr, "Cannot validate vertex which is nullptr");
|
||||
return schema_validator->ValidatePropertyUpdate(vertex_->primary_label, property_id);
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidateAddLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidateRemoveLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -13,83 +13,58 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "storage/v3/vertex_id.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
class EdgeAccessor;
|
||||
class Storage;
|
||||
class Shard;
|
||||
struct Indices;
|
||||
struct Constraints;
|
||||
|
||||
class VertexAccessor final {
|
||||
private:
|
||||
struct VertexValidator {
|
||||
// TODO(jbajic) Beware since vertex is pointer it will be accessed even as nullptr
|
||||
explicit VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex);
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidatePropertyUpdate(PropertyId property_id) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateAddLabel(LabelId label) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateRemoveLabel(LabelId label) const;
|
||||
|
||||
const SchemaValidator *schema_validator;
|
||||
|
||||
private:
|
||||
const Vertex *vertex_;
|
||||
};
|
||||
friend class Storage;
|
||||
friend class Shard;
|
||||
|
||||
public:
|
||||
// Be careful when using VertexAccessor since it can be instantiated with
|
||||
// nullptr values
|
||||
VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config, const SchemaValidator &schema_validator, bool for_deleted = false)
|
||||
VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Config::Items config,
|
||||
const VertexValidator &vertex_validator, bool for_deleted = false)
|
||||
: vertex_(vertex),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
vertex_validator_{schema_validator, vertex},
|
||||
vertex_validator_{&vertex_validator},
|
||||
for_deleted_(for_deleted) {}
|
||||
|
||||
static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, View view);
|
||||
Config::Items config, const VertexValidator &vertex_validator, View view);
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const;
|
||||
|
||||
/// Add a label and return `true` if insertion took place.
|
||||
/// `false` is returned if the label already existed.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> AddLabel(LabelId label);
|
||||
|
||||
/// Add a label and return `true` if insertion took place.
|
||||
/// `false` is returned if the label already existed, or SchemaViolation
|
||||
/// if adding the label has violated one of the schema constraints.
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<bool> AddLabelAndValidate(LabelId label);
|
||||
|
||||
/// Remove a label and return `true` if deletion took place.
|
||||
/// `false` is returned if the vertex did not have a label already.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> RemoveLabel(LabelId label);
|
||||
|
||||
/// Remove a label and return `true` if deletion took place.
|
||||
/// `false` is returned if the vertex did not have a label already. or SchemaViolation
|
||||
/// if adding the label has violated one of the schema constraints.
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<bool> RemoveLabelAndValidate(LabelId label);
|
||||
|
||||
Result<bool> HasLabel(View view, LabelId label) const;
|
||||
|
||||
Result<bool> HasLabel(LabelId label, View view) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
@ -99,9 +74,9 @@ class VertexAccessor final {
|
||||
|
||||
Result<LabelId> PrimaryLabel(View view) const;
|
||||
|
||||
/// Set a property value and return the old value.
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
Result<PrimaryKey> PrimaryKey(View view) const;
|
||||
|
||||
Result<VertexId> Id(View view) const;
|
||||
|
||||
/// Set a property value and return the old value or error.
|
||||
/// @throw std::bad_alloc
|
||||
@ -114,6 +89,9 @@ class VertexAccessor final {
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
||||
|
||||
// TODO Remove this
|
||||
Result<PropertyValue> GetProperty(View view, PropertyId property) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
|
||||
|
||||
@ -121,20 +99,18 @@ class VertexAccessor final {
|
||||
/// @throw std::length_error if the resulting vector exceeds
|
||||
/// std::vector::max_size().
|
||||
Result<std::vector<EdgeAccessor>> InEdges(View view, const std::vector<EdgeTypeId> &edge_types = {},
|
||||
const VertexAccessor *destination = nullptr) const;
|
||||
const VertexId *destination_id = nullptr) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error if the resulting vector exceeds
|
||||
/// std::vector::max_size().
|
||||
Result<std::vector<EdgeAccessor>> OutEdges(View view, const std::vector<EdgeTypeId> &edge_types = {},
|
||||
const VertexAccessor *destination = nullptr) const;
|
||||
const VertexId *destination_id = nullptr) const;
|
||||
|
||||
Result<size_t> InDegree(View view) const;
|
||||
|
||||
Result<size_t> OutDegree(View view) const;
|
||||
|
||||
Gid Gid() const noexcept { return vertex_->gid; }
|
||||
|
||||
const SchemaValidator *GetSchemaValidator() const;
|
||||
|
||||
bool operator==(const VertexAccessor &other) const noexcept {
|
||||
@ -143,12 +119,27 @@ class VertexAccessor final {
|
||||
bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); }
|
||||
|
||||
private:
|
||||
/// Add a label and return `true` if insertion took place.
|
||||
/// `false` is returned if the label already existed.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> AddLabel(LabelId label);
|
||||
|
||||
/// Remove a label and return `true` if deletion took place.
|
||||
/// `false` is returned if the vertex did not have a label already.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> RemoveLabel(LabelId label);
|
||||
|
||||
/// Set a property value and return the old value.
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
|
||||
Result<void> CheckVertexExistence(View view) const;
|
||||
|
||||
Vertex *vertex_;
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
VertexValidator vertex_validator_;
|
||||
const VertexValidator *vertex_validator_;
|
||||
|
||||
// if the accessor was created for a deleted vertex.
|
||||
// Accessor behaves differently for some methods based on this
|
||||
@ -165,6 +156,6 @@ class VertexAccessor final {
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<memgraph::storage::v3::VertexAccessor> {
|
||||
size_t operator()(const memgraph::storage::v3::VertexAccessor &v) const noexcept { return v.Gid().AsUint(); }
|
||||
size_t operator()(const memgraph::storage::v3::VertexAccessor & /*v*/) const noexcept { return 0; }
|
||||
};
|
||||
} // namespace std
|
||||
|
32
src/storage/v3/vertex_id.hpp
Normal file
32
src/storage/v3/vertex_id.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
// TODO(antaljanosbenjamin): It is possible to use a union of the current primary key and a vertex pointer: for local
|
||||
// vertices we can spare some space by eliminating copying the primary label and key, however it might introduce some
|
||||
// overhead for "remove vertices", because of the extra enum that is necessary for this optimization.
|
||||
struct VertexId {
|
||||
VertexId(const LabelId primary_label, PrimaryKey primary_key)
|
||||
: primary_label{primary_label}, primary_key{std::move(primary_key)} {}
|
||||
LabelId primary_label;
|
||||
PrimaryKey primary_key;
|
||||
};
|
||||
|
||||
inline bool operator==(const VertexId &lhs, const VertexId &rhs) {
|
||||
return lhs.primary_label == rhs.primary_label && lhs.primary_key == rhs.primary_key;
|
||||
}
|
||||
} // namespace memgraph::storage::v3
|
19
src/storage/v3/vertices_skip_list.hpp
Normal file
19
src/storage/v3/vertices_skip_list.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
using VerticesSkipList = utils::SkipList<LexicographicallyOrderedVertex>;
|
||||
} // namespace memgraph::storage::v3
|
@ -11,6 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
#include <iterator>
|
||||
|
||||
namespace memgraph::utils {
|
||||
template <typename T, typename... Args>
|
||||
@ -18,4 +19,19 @@ concept SameAsAnyOf = (std::same_as<T, Args> || ...);
|
||||
|
||||
template <typename T>
|
||||
concept Enum = std::is_enum_v<T>;
|
||||
|
||||
// WithRef, CanReference and Dereferenceable is based on the similarly named concepts in GCC 11.2.0
|
||||
// bits/iterator_concepts.h
|
||||
template <typename T>
|
||||
using WithRef = T &;
|
||||
|
||||
template <typename T>
|
||||
concept CanReference = requires {
|
||||
typename WithRef<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept Dereferenceable = requires(T t) {
|
||||
{ *t } -> CanReference;
|
||||
};
|
||||
} // namespace memgraph::utils
|
||||
|
@ -37,7 +37,7 @@ namespace memgraph::logging {
|
||||
// compilers
|
||||
inline void AssertFailed(const char *file_name, int line_num, const char *expr, const std::string &message) {
|
||||
spdlog::critical(
|
||||
"\nAssertion failed in file {} at line {}."
|
||||
"\nAssertion failed at {}:{}."
|
||||
"\n\tExpression: '{}'"
|
||||
"{}",
|
||||
file_name, line_num, expr, !message.empty() ? fmt::format("\n\tMessage: '{}'", message) : "");
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user