Merge branch 'project-pineapples' into T1010-MG-implement-query-engine-client

This commit is contained in:
kostas kyrimis 2022-09-21 18:22:07 +00:00
commit 3fa3f06581
104 changed files with 8427 additions and 9387 deletions
.gitignore
src
tests

4
.gitignore vendored
View File

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

View File

@ -1,10 +1,9 @@
set(coordinator_src_files
coordinator.hpp
shard_map.hpp
hybrid_logical_clock.hpp)
coordinator.cpp
shard_map.cpp)
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)

View File

@ -0,0 +1,129 @@
// 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 <coordinator/coordinator.hpp>
namespace memgraph::coordinator {
CoordinatorWriteResponses Coordinator::ApplyWrite(HeartbeatRequest &&heartbeat_request) {
spdlog::info("Coordinator handling HeartbeatRequest");
// add this storage engine to any under-replicated shards that it is not already a part of
auto initializing_rsms_for_shard_manager =
shard_map_.AssignShards(heartbeat_request.from_storage_manager, heartbeat_request.initialized_rsms);
return HeartbeatResponse{
.shards_to_initialize = initializing_rsms_for_shard_manager,
};
}
CoordinatorWriteResponses Coordinator::ApplyWrite(HlcRequest &&hlc_request) {
HlcResponse res{};
auto hlc_shard_map = shard_map_.GetHlc();
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
res.new_hlc = Hlc{
.logical_id = ++highest_allocated_timestamp_,
// TODO(tyler) probably pass some more context to the Coordinator here
// so that we can use our wall clock and enforce monotonicity.
// .coordinator_wall_clock = io_.Now(),
};
// Allways return fresher shard_map for now.
res.fresher_shard_map = std::make_optional(shard_map_);
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
AllocateEdgeIdBatchResponse res{};
uint64_t low = highest_allocated_edge_id_;
highest_allocated_edge_id_ += ahr.batch_size;
uint64_t high = highest_allocated_edge_id_;
res.low = low;
res.high = high;
return res;
}
/// This splits the shard immediately beneath the provided
/// split key, keeping the assigned peers identical for now,
/// but letting them be gradually migrated over time.
CoordinatorWriteResponses Coordinator::ApplyWrite(SplitShardRequest &&split_shard_request) {
SplitShardResponse res{};
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
res.success = false;
} else {
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
split_shard_request.split_key);
}
return res;
}
/// This adds the provided storage engine to the standby storage engine pool,
/// which can be used to rebalance storage over time.
CoordinatorWriteResponses Coordinator::ApplyWrite(
RegisterStorageEngineRequest && /* register_storage_engine_request */) {
RegisterStorageEngineResponse res{};
// TODO
return res;
}
/// This begins the process of draining the provided storage engine from all raft
/// clusters that it might be participating in.
CoordinatorWriteResponses Coordinator::ApplyWrite(
DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
DeregisterStorageEngineResponse res{};
// TODO
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
InitializeLabelResponse res{};
std::optional<LabelId> new_label_id = shard_map_.InitializeNewLabel(
initialize_label_request.label_name, initialize_label_request.schema, initialize_label_request.replication_factor,
initialize_label_request.last_shard_map_version);
if (new_label_id) {
res.new_label_id = new_label_id.value();
res.fresher_shard_map = std::nullopt;
res.success = true;
} else {
res.fresher_shard_map = shard_map_;
res.success = false;
}
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
AllocatePropertyIdsResponse res{};
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
res.property_ids = property_ids;
return res;
}
} // namespace memgraph::coordinator

View File

@ -12,26 +12,30 @@
#pragma once
#include <optional>
#include <set>
#include <string>
#include <unordered_set>
#include <variant>
#include <vector>
#include "coordinator/hybrid_logical_clock.hpp"
#include "coordinator/shard_map.hpp"
#include "io/simulator/simulator.hpp"
#include "io/time.hpp"
#include "io/transport.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/schemas.hpp"
#include <boost/uuid/uuid.hpp>
#include <coordinator/hybrid_logical_clock.hpp>
#include <coordinator/shard_map.hpp>
#include <io/simulator/simulator.hpp>
#include <io/time.hpp>
#include <io/transport.hpp>
#include <storage/v3/id_types.hpp>
#include <storage/v3/schemas.hpp>
namespace memgraph::coordinator {
using memgraph::io::Address;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::PropertyId;
using Address = memgraph::io::Address;
using SimT = memgraph::io::simulator::SimulatorTransport;
using memgraph::storage::v3::SchemaProperty;
using SimT = memgraph::io::simulator::SimulatorTransport;
using PrimaryKey = std::vector<PropertyValue>;
struct HlcRequest {
Hlc last_shard_map_version;
@ -81,7 +85,7 @@ struct AllocatePropertyIdsResponse {
struct SplitShardRequest {
Hlc previous_shard_map_version;
LabelId label_id;
CompoundKey split_key;
PrimaryKey split_key;
};
struct SplitShardResponse {
@ -107,26 +111,34 @@ struct DeregisterStorageEngineResponse {
struct InitializeLabelRequest {
std::string label_name;
std::vector<SchemaProperty> schema;
size_t replication_factor;
Hlc last_shard_map_version;
};
struct InitializeLabelResponse {
bool success;
LabelId new_label_id;
std::optional<ShardMap> fresher_shard_map;
};
struct HeartbeatRequest {};
struct HeartbeatResponse {};
struct HeartbeatRequest {
Address from_storage_manager;
std::set<boost::uuids::uuid> initialized_rsms;
};
struct HeartbeatResponse {
std::vector<ShardToInitialize> shards_to_initialize;
};
using CoordinatorWriteRequests =
std::variant<HlcRequest, AllocateEdgeIdBatchRequest, SplitShardRequest, RegisterStorageEngineRequest,
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest>;
using CoordinatorWriteResponses =
std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse, RegisterStorageEngineResponse,
DeregisterStorageEngineResponse, InitializeLabelResponse, AllocatePropertyIdsResponse>;
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest, HeartbeatRequest>;
using CoordinatorWriteResponses = std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse,
RegisterStorageEngineResponse, DeregisterStorageEngineResponse,
InitializeLabelResponse, AllocatePropertyIdsResponse, HeartbeatResponse>;
using CoordinatorReadRequests = std::variant<GetShardMapRequest, HeartbeatRequest>;
using CoordinatorReadResponses = std::variant<GetShardMapResponse, HeartbeatResponse>;
using CoordinatorReadRequests = std::variant<GetShardMapRequest>;
using CoordinatorReadResponses = std::variant<GetShardMapResponse>;
class Coordinator {
public:
@ -151,114 +163,34 @@ class Coordinator {
/// Query engines need to periodically request batches of unique edge IDs.
uint64_t highest_allocated_edge_id_;
static CoordinatorReadResponses HandleRead(HeartbeatRequest && /* heartbeat_request */) {
return HeartbeatResponse{};
}
CoordinatorReadResponses HandleRead(GetShardMapRequest && /* get_shard_map_request */) {
GetShardMapResponse res;
res.shard_map = shard_map_;
return res;
}
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request) {
HlcResponse res{};
CoordinatorWriteResponses ApplyWrite(HeartbeatRequest &&heartbeat_request);
auto hlc_shard_map = shard_map_.GetHlc();
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request);
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
res.new_hlc = Hlc{
.logical_id = ++highest_allocated_timestamp_,
// TODO(tyler) probably pass some more context to the Coordinator here
// so that we can use our wall clock and enforce monotonicity.
// .coordinator_wall_clock = io_.Now(),
};
// Allways return fresher shard_map for now.
res.fresher_shard_map = std::make_optional(shard_map_);
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
AllocateEdgeIdBatchResponse res{};
uint64_t low = highest_allocated_edge_id_;
highest_allocated_edge_id_ += ahr.batch_size;
uint64_t high = highest_allocated_edge_id_;
res.low = low;
res.high = high;
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr);
/// This splits the shard immediately beneath the provided
/// split key, keeping the assigned peers identical for now,
/// but letting them be gradually migrated over time.
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request) {
SplitShardResponse res{};
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
res.success = false;
} else {
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
split_shard_request.split_key);
}
return res;
}
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request);
/// This adds the provided storage engine to the standby storage engine pool,
/// which can be used to rebalance storage over time.
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */) {
RegisterStorageEngineResponse res{};
// TODO
return res;
}
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */);
/// This begins the process of draining the provided storage engine from all raft
/// clusters that it might be participating in.
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
DeregisterStorageEngineResponse res{};
// TODO
// const Address &address = register_storage_engine_request.address;
// storage_engine_pool_.erase(address);
// res.success = true;
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */);
return res;
}
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request);
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
InitializeLabelResponse res{};
bool success = shard_map_.InitializeNewLabel(initialize_label_request.label_name, initialize_label_request.schema,
initialize_label_request.last_shard_map_version);
if (success) {
res.fresher_shard_map = shard_map_;
res.success = false;
} else {
res.fresher_shard_map = std::nullopt;
res.success = true;
}
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
AllocatePropertyIdsResponse res{};
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
res.property_ids = property_ids;
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request);
};
} // namespace memgraph::coordinator

View File

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

View File

@ -0,0 +1,90 @@
// 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 "coordinator/shard_map.hpp"
#include "storage/v3/temporal.hpp"
namespace memgraph::coordinator {
using memgraph::common::SchemaType;
using memgraph::storage::v3::TemporalData;
using memgraph::storage::v3::TemporalType;
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema) {
PrimaryKey ret{};
const int64_t min_int = std::numeric_limits<int64_t>::min();
const TemporalData date{TemporalType::Date, min_int};
const TemporalData local_time{TemporalType::LocalTime, min_int};
const TemporalData local_date_time{TemporalType::LocalDateTime, min_int};
const TemporalData duration{TemporalType::Duration, min_int};
for (const auto &schema_property : schema) {
switch (schema_property.type) {
case SchemaType::BOOL:
ret.emplace_back(PropertyValue(false));
break;
case SchemaType::INT:
ret.emplace_back(PropertyValue(min_int));
break;
case SchemaType::STRING:
ret.emplace_back(PropertyValue(""));
break;
case SchemaType::DATE:
ret.emplace_back(PropertyValue(date));
break;
case SchemaType::LOCALTIME:
ret.emplace_back(PropertyValue(local_time));
break;
case SchemaType::LOCALDATETIME:
ret.emplace_back(PropertyValue(local_date_time));
break;
case SchemaType::DURATION:
ret.emplace_back(PropertyValue(duration));
break;
}
}
return ret;
}
std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
size_t replication_factor, Hlc last_shard_map_version) {
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
return std::nullopt;
}
const LabelId label_id = LabelId::FromUint(++max_label_id);
labels.emplace(std::move(label_name), label_id);
PrimaryKey initial_key = SchemaToMinKey(schema);
Shard empty_shard = {};
Shards shards = {
{initial_key, empty_shard},
};
LabelSpace label_space{
.schema = std::move(schema),
.shards = shards,
.replication_factor = replication_factor,
};
label_spaces.emplace(label_id, label_space);
IncrementShardMapVersion();
return label_id;
}
} // namespace memgraph::coordinator

View File

@ -11,11 +11,18 @@
#pragma once
#include <limits>
#include <map>
#include <set>
#include <vector>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include "common/types.hpp"
#include "coordinator/hybrid_logical_clock.hpp"
#include "io/address.hpp"
#include "storage/v3/config.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/schemas.hpp"
@ -23,8 +30,10 @@
namespace memgraph::coordinator {
using memgraph::io::Address;
using memgraph::storage::v3::Config;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::PropertyId;
using memgraph::storage::v3::PropertyValue;
using memgraph::storage::v3::SchemaProperty;
enum class Status : uint8_t {
@ -41,16 +50,27 @@ struct AddressAndStatus {
friend bool operator<(const AddressAndStatus &lhs, const AddressAndStatus &rhs) { return lhs.address < rhs.address; }
};
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
using PrimaryKey = std::vector<PropertyValue>;
using Shard = std::vector<AddressAndStatus>;
using Shards = std::map<CompoundKey, Shard>;
using Shards = std::map<PrimaryKey, Shard>;
using LabelName = std::string;
using PropertyName = std::string;
using PropertyMap = std::map<PropertyName, PropertyId>;
struct ShardToInitialize {
boost::uuids::uuid uuid;
LabelId label_id;
PrimaryKey min_key;
std::optional<PrimaryKey> max_key;
Config config;
};
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
struct LabelSpace {
std::vector<SchemaProperty> schema;
std::map<CompoundKey, Shard> shards;
std::map<PrimaryKey, Shard> shards;
size_t replication_factor;
};
struct ShardMap {
@ -77,7 +97,70 @@ struct ShardMap {
Hlc GetHlc() const noexcept { return shard_map_version; }
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const CompoundKey &key) {
// Returns the shard UUIDs that have been assigned but not yet acknowledged for this storage manager
std::vector<ShardToInitialize> AssignShards(Address storage_manager, std::set<boost::uuids::uuid> initialized) {
std::vector<ShardToInitialize> ret{};
bool mutated = false;
for (auto &[label_id, label_space] : label_spaces) {
for (auto &[low_key, shard] : label_space.shards) {
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
bool machine_contains_shard = false;
for (auto &aas : shard) {
if (initialized.contains(aas.address.unique_id)) {
spdlog::info("marking shard as full consensus participant: {}", aas.address.unique_id);
aas.status = Status::CONSENSUS_PARTICIPANT;
machine_contains_shard = true;
} else {
const bool same_machine = aas.address.last_known_ip == storage_manager.last_known_ip &&
aas.address.last_known_port == storage_manager.last_known_port;
if (same_machine) {
machine_contains_shard = true;
ret.push_back(ShardToInitialize{
.uuid = aas.address.unique_id,
.label_id = label_id,
.min_key = low_key,
.max_key = std::nullopt,
.config = Config{},
});
}
}
}
if (!machine_contains_shard && shard.size() < label_space.replication_factor) {
Address address = storage_manager;
// TODO(tyler) use deterministic UUID so that coordinators don't diverge here
address.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
ret.push_back(ShardToInitialize{
.uuid = address.unique_id,
.label_id = label_id,
.min_key = low_key,
.max_key = std::nullopt,
.config = Config{},
});
AddressAndStatus aas = {
.address = address,
.status = Status::INITIALIZING,
};
shard.emplace_back(aas);
}
}
}
if (mutated) {
IncrementShardMapVersion();
}
return ret;
}
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
if (previous_shard_map_version != shard_map_version) {
return false;
}
@ -85,10 +168,11 @@ struct ShardMap {
auto &label_space = label_spaces.at(label_id);
auto &shards_in_map = label_space.shards;
MG_ASSERT(!shards_in_map.empty());
MG_ASSERT(!shards_in_map.contains(key));
MG_ASSERT(label_spaces.contains(label_id));
// Finding the Shard that the new CompoundKey should map to.
// Finding the Shard that the new PrimaryKey should map to.
auto prev = std::prev(shards_in_map.upper_bound(key));
Shard duplicated_shard = prev->second;
@ -98,34 +182,16 @@ struct ShardMap {
return true;
}
bool InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema, Hlc last_shard_map_version) {
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
return false;
}
const LabelId label_id = LabelId::FromUint(++max_label_id);
labels.emplace(std::move(label_name), label_id);
LabelSpace label_space{
.schema = std::move(schema),
.shards = Shards{},
};
label_spaces.emplace(label_id, label_space);
IncrementShardMapVersion();
return true;
}
std::optional<LabelId> InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
size_t replication_factor, Hlc last_shard_map_version);
void AddServer(Address server_address) {
// Find a random place for the server to plug in
}
LabelId GetLabelId(const std::string &label) const { return labels.at(label); }
Shards GetShardsForRange(const LabelName &label_name, const CompoundKey &start_key,
const CompoundKey &end_key) const {
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const {
MG_ASSERT(start_key <= end_key);
MG_ASSERT(labels.contains(label_name));
@ -148,7 +214,7 @@ struct ShardMap {
return shards;
}
Shard GetShardForKey(const LabelName &label_name, const CompoundKey &key) const {
Shard GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
MG_ASSERT(labels.contains(label_name));
LabelId label_id = labels.at(label_name);
@ -161,7 +227,7 @@ struct ShardMap {
return std::prev(label_space.shards.upper_bound(key))->second;
}
Shard GetShardForKey(const LabelId &label_id, const CompoundKey &key) const {
Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
MG_ASSERT(label_spaces.contains(label_id));
const auto &label_space = label_spaces.at(label_id);

View File

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

View File

@ -32,7 +32,7 @@ struct StorageTag {};
struct QueryEngineTag {};
template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId,
typename PropertyValue, typename ConvFunction, typename Error, typename Tag = StorageTag>
typename PropertyValue, typename ConvFunctor, typename Error, typename Tag = StorageTag>
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
public:
ExpressionEvaluator(Frame<TypedValue> *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx,
@ -729,6 +729,62 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
return conv_(maybe_prop);
}
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
TypedValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
// This is a very nasty and temporary hack in order to make MERGE work.
// The old storage had the following logic when returning an `OLD` view:
// `return old ? old : new`. That means that if the `OLD` view didn't
// exist, it returned the NEW view. With this hack we simulate that
// behavior.
// TODO (mferencevic, teon.banek): Remove once MERGE is reimplemented.
maybe_prop = record_accessor.GetProperty(StorageView::NEW, ctx_->properties[prop.ix]);
}
if (maybe_prop.HasError()) {
switch (maybe_prop.GetError()) {
case Error::DELETED_OBJECT:
throw ExpressionRuntimeException("Trying to get a property from a deleted object.");
case Error::NONEXISTENT_OBJECT:
throw ExpressionRuntimeException("Trying to get a property from an object that doesn't exist.");
case Error::SERIALIZATION_ERROR:
case Error::VERTEX_HAS_EDGES:
case Error::PROPERTIES_DISABLED:
throw ExpressionRuntimeException("Unexpected error when getting a property.");
}
}
return conv_(*maybe_prop, ctx_->memory);
}
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
TypedValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
auto maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
// This is a very nasty and temporary hack in order to make MERGE work.
// The old storage had the following logic when returning an `OLD` view:
// `return old ? old : new`. That means that if the `OLD` view didn't
// exist, it returned the NEW view. With this hack we simulate that
// behavior.
// TODO (mferencevic, teon.banek): Remove once MERGE is reimplemented.
maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
}
if (maybe_prop.HasError()) {
switch (maybe_prop.GetError()) {
case Error::DELETED_OBJECT:
throw ExpressionRuntimeException("Trying to get a property from a deleted object.");
case Error::NONEXISTENT_OBJECT:
throw ExpressionRuntimeException("Trying to get a property from an object that doesn't exist.");
case Error::SERIALIZATION_ERROR:
case Error::VERTEX_HAS_EDGES:
case Error::PROPERTIES_DISABLED:
throw ExpressionRuntimeException("Unexpected error when getting a property.");
}
}
return conv_(*maybe_prop, ctx_->memory);
}
LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
Frame<TypedValue> *frame_;
@ -737,7 +793,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.

View File

@ -99,7 +99,7 @@ class Shared {
bool IsReady() const {
std::unique_lock<std::mutex> lock(mu_);
return item_;
return item_.has_value();
}
std::optional<T> TryGet() {
@ -148,12 +148,6 @@ class Future {
old.consumed_or_moved_ = true;
}
Future &operator=(Future &&old) noexcept {
MG_ASSERT(!old.consumed_or_moved_, "Future moved from after already being moved from or consumed.");
shared_ = std::move(old.shared_);
old.consumed_or_moved_ = true;
}
Future(const Future &) = delete;
Future &operator=(const Future &) = delete;
~Future() = default;

View File

@ -25,7 +25,7 @@ class LocalSystem {
public:
Io<LocalTransport> Register(Address address) {
LocalTransport local_transport(local_transport_handle_, address);
LocalTransport local_transport(local_transport_handle_);
return Io{local_transport, address};
}

View File

@ -25,18 +25,16 @@ namespace memgraph::io::local_transport {
class LocalTransport {
std::shared_ptr<LocalTransportHandle> local_transport_handle_;
const Address address_;
public:
LocalTransport(std::shared_ptr<LocalTransportHandle> local_transport_handle, Address address)
: local_transport_handle_(std::move(local_transport_handle)), address_(address) {}
explicit LocalTransport(std::shared_ptr<LocalTransportHandle> local_transport_handle)
: local_transport_handle_(std::move(local_transport_handle)) {}
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address to_address, RequestId request_id, RequestT request, Duration timeout) {
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestId request_id, RequestT request,
Duration timeout) {
auto [future, promise] = memgraph::io::FuturePromisePair<ResponseResult<ResponseT>>();
Address from_address = address_;
local_transport_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
std::move(promise));
@ -44,9 +42,8 @@ class LocalTransport {
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
Address from_address = address_;
return local_transport_handle_->template Receive<Ms...>(timeout);
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
return local_transport_handle_->template Receive<Ms...>(receiver_address, timeout);
}
template <Message M>

View File

@ -36,6 +36,13 @@ class LocalTransportHandle {
std::vector<OpaqueMessage> can_receive_;
public:
~LocalTransportHandle() {
for (auto &&[pk, promise] : promises_) {
std::move(promise.promise).TimeOut();
}
promises_.clear();
}
void ShutDown() {
std::unique_lock<std::mutex> lock(mu_);
should_shut_down_ = true;
@ -53,11 +60,13 @@ class LocalTransportHandle {
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address /* receiver_address */, Duration timeout) {
std::unique_lock lock(mu_);
Time before = Now();
spdlog::info("can_receive_ size: {}", can_receive_.size());
while (can_receive_.empty()) {
Time now = Now();
@ -89,23 +98,26 @@ class LocalTransportHandle {
template <Message M>
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
std::any message_any(std::forward<M>(message));
OpaqueMessage opaque_message{
.from_address = from_address, .request_id = request_id, .message = std::move(message_any)};
OpaqueMessage opaque_message{.to_address = to_address,
.from_address = from_address,
.request_id = request_id,
.message = std::move(message_any)};
PromiseKey promise_key{.requester_address = to_address,
.request_id = opaque_message.request_id,
.replier_address = opaque_message.from_address};
PromiseKey promise_key{
.requester_address = to_address, .request_id = opaque_message.request_id, .replier_address = from_address};
{
std::unique_lock<std::mutex> lock(mu_);
if (promises_.contains(promise_key)) {
spdlog::info("using message to fill promise");
// complete waiting promise if it's there
DeadlineAndOpaquePromise dop = std::move(promises_.at(promise_key));
promises_.erase(promise_key);
dop.promise.Fill(std::move(opaque_message));
} else {
spdlog::info("placing message in can_receive_");
can_receive_.emplace_back(std::move(opaque_message));
}
} // lock dropped

View File

@ -203,4 +203,30 @@ struct DeadlineAndOpaquePromise {
OpaquePromise promise;
};
template <class From>
std::type_info const &type_info_for_variant(From const &from) {
return std::visit([](auto &&x) -> decltype(auto) { return typeid(x); }, from);
}
template <typename From, typename Return, typename Head, typename... Rest>
std::optional<Return> ConvertVariantInner(From &&a) {
if (typeid(Head) == type_info_for_variant(a)) {
Head concrete = std::get<Head>(std::forward<From>(a));
return concrete;
}
if constexpr (sizeof...(Rest) > 0) {
return ConvertVariantInner<From, Return, Rest...>(std::forward<From>(a));
} else {
return std::nullopt;
}
}
/// This function converts a variant to another variant holding a subset OR superset of
/// possible types.
template <class From, class... Ms>
requires(sizeof...(Ms) > 0) std::optional<std::variant<Ms...>> ConvertVariant(From &&from) {
return ConvertVariantInner<From, std::variant<Ms...>, Ms...>(std::forward<From>(from));
}
} // namespace memgraph::io

48
src/io/messages.hpp Normal file
View File

@ -0,0 +1,48 @@
// 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 <variant>
#include <coordinator/coordinator.hpp>
#include <io/rsm/raft.hpp>
#include <io/rsm/shard_rsm.hpp>
#include "utils/concepts.hpp"
namespace memgraph::io::messages {
using memgraph::coordinator::CoordinatorReadRequests;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
// TODO(everbody) change these to the real shard messages
using memgraph::io::rsm::StorageReadRequest;
using memgraph::io::rsm::StorageWriteRequest;
using memgraph::io::rsm::AppendRequest;
using memgraph::io::rsm::AppendResponse;
using memgraph::io::rsm::ReadRequest;
using memgraph::io::rsm::VoteRequest;
using memgraph::io::rsm::VoteResponse;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using CoordinatorMessages =
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>;
using ShardMessages = std::variant<ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, AppendResponse,
WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>;
using ShardManagerMessages = std::variant<WriteResponse<CoordinatorWriteResponses>>;
} // namespace memgraph::io::messages

View File

@ -53,7 +53,6 @@ static constexpr size_t kMaximumAppendBatchSize = 1024;
using Term = uint64_t;
using LogIndex = uint64_t;
using LogSize = uint64_t;
using RequestId = uint64_t;
template <typename WriteOperation>
struct WriteRequest {
@ -230,22 +229,60 @@ class Raft {
Io<IoImpl> io_;
std::vector<Address> peers_;
ReplicatedState replicated_state_;
Time next_cron_;
public:
Raft(Io<IoImpl> &&io, std::vector<Address> peers, ReplicatedState &&replicated_state)
: io_(std::forward<Io<IoImpl>>(io)),
peers_(peers),
replicated_state_(std::forward<ReplicatedState>(replicated_state)) {}
replicated_state_(std::forward<ReplicatedState>(replicated_state)) {
if (peers.empty()) {
role_ = Leader{};
}
}
/// Periodic protocol maintenance. Returns the time that Cron should be called again
/// in the future.
Time Cron() {
// dispatch periodic logic based on our role to a specific Cron method.
std::optional<Role> new_role = std::visit([&](auto &role) { return Cron(role); }, role_);
if (new_role) {
role_ = std::move(new_role).value();
}
const Duration random_cron_interval = RandomTimeout(kMinimumCronInterval, kMaximumCronInterval);
return io_.Now() + random_cron_interval;
}
/// Returns the Address for our underlying Io implementation
Address GetAddress() { return io_.GetAddress(); }
using ReceiveVariant = std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
WriteRequest<WriteOperation>, VoteRequest, VoteResponse>;
void Handle(ReceiveVariant &&message_variant, RequestId request_id, Address from_address) {
// dispatch the message to a handler based on our role,
// which can be specified in the Handle first argument,
// or it can be `auto` if it's a handler for several roles
// or messages.
std::optional<Role> new_role = std::visit(
[&](auto &&msg, auto &role) mutable {
return Handle(role, std::forward<decltype(msg)>(msg), request_id, from_address);
},
std::forward<ReceiveVariant>(message_variant), role_);
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
if (new_role) {
role_ = std::move(new_role).value();
}
}
void Run() {
Time last_cron = io_.Now();
while (!io_.ShouldShutDown()) {
const auto now = io_.Now();
const Duration random_cron_interval = RandomTimeout(kMinimumCronInterval, kMaximumCronInterval);
if (now - last_cron > random_cron_interval) {
Cron();
last_cron = now;
if (now >= next_cron_) {
next_cron_ = Cron();
}
const Duration receive_timeout = RandomTimeout(kMinimumReceiveTimeout, kMaximumReceiveTimeout);
@ -449,16 +486,6 @@ class Raft {
/// been received.
/////////////////////////////////////////////////////////////
/// Periodic protocol maintenance.
void Cron() {
// dispatch periodic logic based on our role to a specific Cron method.
std::optional<Role> new_role = std::visit([&](auto &role) { return Cron(role); }, role_);
if (new_role) {
role_ = std::move(new_role).value();
}
}
// Raft paper - 5.2
// Candidates keep sending Vote to peers until:
// 1. receiving Append with a higher term (become Follower)
@ -541,26 +568,6 @@ class Raft {
/// message that has been received.
/////////////////////////////////////////////////////////////
using ReceiveVariant = std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
WriteRequest<WriteOperation>, VoteRequest, VoteResponse>;
void Handle(ReceiveVariant &&message_variant, RequestId request_id, Address from_address) {
// dispatch the message to a handler based on our role,
// which can be specified in the Handle first argument,
// or it can be `auto` if it's a handler for several roles
// or messages.
std::optional<Role> new_role = std::visit(
[&](auto &&msg, auto &role) mutable {
return Handle(role, std::forward<decltype(msg)>(msg), request_id, from_address);
},
std::forward<ReceiveVariant>(message_variant), role_);
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
if (new_role) {
role_ = std::move(new_role).value();
}
}
// all roles can receive Vote and possibly become a follower
template <AllRoles ALL>
std::optional<Role> Handle(ALL & /* variable */, VoteRequest &&req, RequestId request_id, Address from_address) {
@ -906,7 +913,11 @@ class Raft {
leader.pending_client_requests.emplace(log_index, pcr);
BroadcastAppendEntries(leader.followers);
if (peers_.empty()) {
BumpCommitIndexAndReplyToClients(leader);
} else {
BroadcastAppendEntries(leader.followers);
}
return std::nullopt;
}

View File

@ -33,19 +33,21 @@ class SimulatorTransport {
: simulator_handle_(simulator_handle), address_(address), rng_(std::mt19937{seed}) {}
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address address, uint64_t request_id, RequestT request, Duration timeout) {
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, uint64_t request_id, RequestT request,
Duration timeout) {
std::function<bool()> maybe_tick_simulator = [this] { return simulator_handle_->MaybeTickSimulator(); };
auto [future, promise] =
memgraph::io::FuturePromisePairWithNotifier<ResponseResult<ResponseT>>(maybe_tick_simulator);
simulator_handle_->SubmitRequest(address, address_, request_id, std::move(request), timeout, std::move(promise));
simulator_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
std::move(promise));
return std::move(future);
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
return simulator_handle_->template Receive<Ms...>(address_, timeout);
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
return simulator_handle_->template Receive<Ms...>(receiver_address, timeout);
}
template <Message M>

View File

@ -83,23 +83,26 @@ class Io {
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> RequestWithTimeout(Address address, RequestT request, Duration timeout) {
const RequestId request_id = ++request_id_counter_;
return implementation_.template Request<RequestT, ResponseT>(address, request_id, request, timeout);
const Address from_address = address_;
return implementation_.template Request<RequestT, ResponseT>(address, from_address, request_id, request, timeout);
}
/// Issue a request that times out after the default timeout. This tends
/// to be used by clients.
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address address, RequestT request) {
ResponseFuture<ResponseT> Request(Address to_address, RequestT request) {
const RequestId request_id = ++request_id_counter_;
const Duration timeout = default_timeout_;
return implementation_.template Request<RequestT, ResponseT>(address, request_id, std::move(request), timeout);
const Address from_address = address_;
return implementation_.template Request<RequestT, ResponseT>(to_address, from_address, request_id,
std::move(request), timeout);
}
/// Wait for an explicit number of microseconds for a request of one of the
/// provided types to arrive. This tends to be used by servers.
template <Message... Ms>
RequestResult<Ms...> ReceiveWithTimeout(Duration timeout) {
return implementation_.template Receive<Ms...>(timeout);
return implementation_.template Receive<Ms...>(address_, timeout);
}
/// Wait the default number of microseconds for a request of one of the
@ -107,7 +110,7 @@ class Io {
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive() {
const Duration timeout = default_timeout_;
return implementation_.template Receive<Ms...>(timeout);
return implementation_.template Receive<Ms...>(address_, timeout);
}
/// Send a message in a best-effort fashion. This is used for messaging where
@ -134,5 +137,8 @@ class Io {
}
Address GetAddress() { return address_; }
void SetAddress(Address address) { address_ = address; }
Io<I> ForkLocal() { return Io(implementation_, address_.ForkUniqueAddress()); }
};
}; // namespace memgraph::io

View 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 <boost/asio/ip/tcp.hpp>
#include "io/address.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/schemas.hpp"
namespace memgraph::machine_manager {
using memgraph::io::Address;
using memgraph::storage::v3::SchemaProperty;
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
struct InitialLabelSpace {
std::string label_name;
std::vector<SchemaProperty> schema;
size_t replication_factor;
std::vector<CompoundKey> split_points;
};
struct MachineConfig {
std::vector<InitialLabelSpace> initial_label_spaces;
std::vector<Address> coordinator_addresses;
bool is_storage;
bool is_coordinator;
bool is_query_engine;
boost::asio::ip::address listen_ip;
uint16_t listen_port;
};
} // namespace memgraph::machine_manager

View File

@ -0,0 +1,180 @@
// 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 <boost/uuid/uuid.hpp>
#include <coordinator/coordinator_rsm.hpp>
#include <io/message_conversion.hpp>
#include <io/messages.hpp>
#include <io/rsm/rsm_client.hpp>
#include <io/time.hpp>
#include <machine_manager/machine_config.hpp>
#include <storage/v3/shard_manager.hpp>
namespace memgraph::machine_manager {
using memgraph::coordinator::Coordinator;
using memgraph::coordinator::CoordinatorReadRequests;
using memgraph::coordinator::CoordinatorReadResponses;
using memgraph::coordinator::CoordinatorRsm;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using memgraph::io::ConvertVariant;
using memgraph::io::Duration;
using memgraph::io::RequestId;
using memgraph::io::Time;
using memgraph::io::messages::CoordinatorMessages;
using memgraph::io::messages::ShardManagerMessages;
using memgraph::io::messages::ShardMessages;
using memgraph::io::rsm::AppendRequest;
using memgraph::io::rsm::AppendResponse;
using memgraph::io::rsm::ReadRequest;
using memgraph::io::rsm::VoteRequest;
using memgraph::io::rsm::VoteResponse;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using memgraph::storage::v3::ShardManager;
using memgraph::io::rsm::StorageReadRequest;
using memgraph::io::rsm::StorageWriteRequest;
/// The MachineManager is responsible for:
/// * starting the entire system and ensuring that high-level
/// operational requirements continue to be met
/// * acting as a machine's single caller of Io::Receive
/// * routing incoming messages from the Io interface to the
/// appropriate Coordinator or to the StorageManager
/// (it's not necessary to route anything in this layer
/// to the query engine because the query engine only
/// communicates using higher-level Futures that will
/// be filled immediately when the response comes in
/// at the lower-level transport layer.
///
/// Every storage engine has exactly one RsmEngine.
template <typename IoImpl>
class MachineManager {
io::Io<IoImpl> io_;
MachineConfig config_;
CoordinatorRsm<IoImpl> coordinator_;
ShardManager<IoImpl> shard_manager_;
Time next_cron_;
public:
// TODO initialize ShardManager with "real" coordinator addresses instead of io.GetAddress
// which is only true for single-machine config.
MachineManager(io::Io<IoImpl> io, MachineConfig config, Coordinator coordinator)
: io_(io),
config_(config),
coordinator_{std::move(io.ForkLocal()), {}, std::move(coordinator)},
shard_manager_(ShardManager{io.ForkLocal(), coordinator_.GetAddress()}) {}
Address CoordinatorAddress() { return coordinator_.GetAddress(); }
void Run() {
while (!io_.ShouldShutDown()) {
const auto now = io_.Now();
if (now >= next_cron_) {
next_cron_ = Cron();
}
Duration receive_timeout = next_cron_ - now;
// Note: this parameter pack must be kept in-sync with the ReceiveWithTimeout parameter pack below
using AllMessages =
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse,
WriteResponse<CoordinatorWriteResponses>, ReadRequest<StorageReadRequest>,
AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>;
spdlog::info("MM waiting on Receive");
// Note: this parameter pack must be kept in-sync with the AllMessages parameter pack above
auto request_result = io_.template ReceiveWithTimeout<
ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse, WriteResponse<CoordinatorWriteResponses>,
ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>(
receive_timeout);
if (request_result.HasError()) {
// time to do Cron
spdlog::info("MM got timeout");
continue;
}
auto &&request_envelope = std::move(request_result.GetValue());
spdlog::info("MM got message to {}", request_envelope.to_address.ToString());
// If message is for the coordinator, cast it to subset and pass it to the coordinator
bool to_coordinator = coordinator_.GetAddress() == request_envelope.to_address;
spdlog::info("coordinator: {}", coordinator_.GetAddress().ToString());
if (to_coordinator) {
std::optional<CoordinatorMessages> conversion_attempt =
ConvertVariant<AllMessages, ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>,
AppendResponse, WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>(
std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "coordinator message conversion failed");
spdlog::info("got coordinator message");
CoordinatorMessages &&cm = std::move(conversion_attempt.value());
coordinator_.Handle(std::forward<CoordinatorMessages>(cm), request_envelope.request_id,
request_envelope.from_address);
continue;
}
bool to_sm = shard_manager_.GetAddress() == request_envelope.to_address;
spdlog::info("smm: {}", shard_manager_.GetAddress().ToString());
if (to_sm) {
std::optional<ShardManagerMessages> conversion_attempt =
ConvertVariant<AllMessages, WriteResponse<CoordinatorWriteResponses>>(std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "shard manager message conversion failed");
spdlog::info("got shard manager message");
ShardManagerMessages &&smm = std::move(conversion_attempt.value());
shard_manager_.Receive(std::forward<ShardManagerMessages>(smm), request_envelope.request_id,
request_envelope.from_address);
continue;
}
// treat this as a message to a specific shard rsm and cast it accordingly
std::optional<ShardMessages> conversion_attempt =
ConvertVariant<AllMessages, ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>,
AppendResponse, WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>(
std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "shard rsm message conversion failed for {}",
request_envelope.to_address.ToString());
spdlog::info("got shard rsm message");
ShardMessages &&sm = std::move(conversion_attempt.value());
shard_manager_.Route(std::forward<ShardMessages>(sm), request_envelope.request_id, request_envelope.to_address,
request_envelope.from_address);
}
}
private:
Time Cron() {
spdlog::info("running MachineManager::Cron, address {}", io_.GetAddress().ToString());
return shard_manager_.Cron();
}
};
} // namespace memgraph::machine_manager

View File

@ -20,8 +20,6 @@
#include "query/v2/db_accessor.hpp"
#include "query/v2/requests.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"
@ -39,8 +37,8 @@ class Callable {
} // namespace detail
using ExpressionEvaluator =
memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View,
storage::v3::LabelId, msgs::Value, detail::Callable,
memgraph::expr::ExpressionEvaluator<TypedValue, memgraph::query::v2::EvaluationContext, DbAccessor,
storage::v3::View, storage::v3::LabelId, msgs::Value, detail::Callable,
memgraph::storage::v3::Error, memgraph::expr::QueryEngineTag>;
} // namespace memgraph::query::v2

View File

@ -263,8 +263,6 @@ class DbAccessor final {
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) {
@ -360,17 +358,13 @@ class DbAccessor final {
// 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 storage::v3::PropertyId::FromUint(0);
}
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 storage::v3::LabelId::FromUint(0); }
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 storage::v3::EdgeTypeId::FromUint(0);
}
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); }
@ -380,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(); }
@ -411,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(); }

View File

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

View File

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

View File

@ -127,135 +127,25 @@ std::optional<std::string> GetOptionalStringValue(query::v2::Expression *express
class ReplQueryHandler final : public query::v2::ReplicationQueryHandler {
public:
explicit ReplQueryHandler(storage::v3::Shard *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::Shard *db_;
std::vector<Replica> ShowReplicas() const override { return {}; }
};
/// returns false if the replication role can't be set
/// @throw QueryRuntimeException if an error ocurred.
@ -742,7 +632,6 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
auto *db = interpreter_context->db;
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));
}
@ -944,8 +833,8 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
in_explicit_transaction_ = true;
expect_rollback_ = false;
db_accessor_ =
std::make_unique<storage::v3::Shard::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()) {
@ -1217,7 +1106,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
}
};
auto label = interpreter_context->NameToLabelId(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;
@ -1368,48 +1257,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};
}
constexpr auto ToStorageIsolationLevel(const IsolationLevelQuery::IsolationLevel isolation_level) noexcept {
@ -1458,24 +1321,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::Shard::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) {
@ -1543,7 +1389,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()))}};
@ -1568,28 +1413,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),
@ -1613,185 +1437,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->NameToLabelId(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->NameToPropertyId(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,
@ -1885,8 +1531,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::Shard::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());
}
@ -1959,13 +1605,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);
@ -1991,30 +1630,14 @@ void Interpreter::Commit() {
// a query.
if (!db_accessor_) return;
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());
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); });
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
property_names_stream.str());
break;
}
}
}
const auto reset_necessary_members = [this]() {
execution_db_accessor_.reset();
db_accessor_.reset();
};
db_accessor_->Commit(coordinator::Hlc{});
reset_necessary_members();
SPDLOG_DEBUG("Finished committing the transaction");
}

View File

@ -49,6 +49,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"

View File

@ -551,12 +551,21 @@ 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 = std::variant<CreateVerticesRequest, DeleteVerticesRequest, UpdateVerticesRequest,
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest>;
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesResponse, UpdateVerticesResponse,
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse>;
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;
} // namespace memgraph::msgs

View File

@ -127,7 +127,7 @@ class ShardRequestManager : public ShardRequestManagerInterface {
using Address = memgraph::io::Address;
using Shard = memgraph::coordinator::Shard;
using ShardMap = memgraph::coordinator::ShardMap;
using CompoundKey = memgraph::coordinator::CompoundKey;
using CompoundKey = memgraph::coordinator::PrimaryKey;
using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor;
ShardRequestManager(CoordinatorClient coord, memgraph::io::Io<TTransport> &&io)
: coord_cli_(std::move(coord)), io_(std::move(io)) {}

View File

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

View File

@ -1,11 +1,12 @@
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
@ -15,30 +16,18 @@ set(storage_v3_src_files
schemas.cpp
schema_validator.cpp
shard.cpp
storage.cpp
shard_rsm.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})
bindings/typed_value.cpp
shard_rsm.cpp
storage.cpp)
# ######################
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)

File diff suppressed because it is too large Load Diff

View File

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

View 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)

View File

@ -11,13 +11,10 @@
#pragma once
#include "utils/exceptions.hpp"
#include "storage/v3/bindings/bindings.hpp"
namespace memgraph::storage::v3::durability {
#include "expr/ast/cypher_main_visitor.hpp"
/// Exception used to handle errors during recovery.
class RecoveryFailure : public utils::BasicException {
using utils::BasicException::BasicException;
};
} // namespace memgraph::storage::v3::durability
namespace memgraph::storage::v3 {
using CypherMainVisitor = memgraph::expr::CypherMainVisitor;
} // namespace memgraph::storage::v3

View 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

View 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

View 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

View 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

View 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

View 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"

View 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

View 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

View 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

View File

@ -1,108 +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) {
Block *block = FindOrCreateBlock(id);
block->field[(id % kIdsInBlock) / kIdsInField] |= 1ULL << (id % kIdsInField);
if (id == oldest_active_) {
UpdateOldestActive();
}
}
uint64_t CommitLog::OldestActive() const noexcept { 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

View File

@ -1,78 +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() const noexcept;
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::Allocator<Block> allocator_;
};
} // namespace memgraph::storage::v3

View File

@ -14,6 +14,8 @@
#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"
@ -24,36 +26,14 @@ namespace memgraph::storage::v3 {
/// the storage. This class also defines the default behavior.
struct Config {
struct Gc {
// TODO(antaljanosbenjamin): How to handle garbage collection?
enum class Type { NONE };
Type type{Type::NONE};
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;

View File

@ -1,414 +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};
{
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};
{
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, VerticesSkipList::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 auto &lo_vertex : vertices) {
const auto &vertex = lo_vertex.vertex;
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

View File

@ -1,202 +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 "storage/v3/vertices_skip_list.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,
VerticesSkipList::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,
VerticesSkipList::Accessor vertices) {
if (utils::Contains(constraints->existence_constraints, std::make_pair(label, property))) {
return false;
}
for (const auto &lgo_vertex : vertices) {
const auto &vertex = lgo_vertex.vertex;
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

View File

@ -12,6 +12,7 @@
#include "expr/typed_value.hpp"
#include "query/v2/requests.hpp"
#include "storage/v3/property_value.hpp"
#include "utils/memory.hpp"
#pragma once
@ -68,6 +69,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()) {

View File

@ -11,9 +11,7 @@
#pragma once
#include <atomic>
#include <memory>
#include "storage/v3/edge_ref.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
@ -26,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
@ -63,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!");
}
@ -95,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) {
@ -159,47 +157,47 @@ 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, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
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, std::move(vertex_id), edge}) {}
Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
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, std::move(vertex_id), edge}) {}
Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, 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, std::move(vertex_id), edge}) {}
Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, 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, std::move(vertex_id), edge}) {}
@ -220,6 +218,7 @@ struct Delta {
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();
break;
@ -229,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;

View File

@ -1,346 +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, VerticesSkipList *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,
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, 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

View File

@ -1,112 +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 <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, VerticesSkipList *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,
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, 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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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, VerticesSkipList *vertices,
utils::SkipList<Edge> *edges,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
NameIdMapper *name_id_mapper, 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,
VerticesSkipList *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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,262 +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 "storage/v3/vertices_skip_list.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, VerticesSkipList *vertices,
utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, 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

View File

@ -92,6 +92,10 @@ 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();
auto exists = true;

View File

@ -28,7 +28,6 @@ namespace memgraph::storage::v3 {
struct Vertex;
class VertexAccessor;
struct Indices;
struct Constraints;
class EdgeAccessor final {
private:
@ -36,14 +35,13 @@ class EdgeAccessor final {
public:
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, VertexId from_vertex, VertexId to_vertex, Transaction *transaction,
Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false)
Indices *indices, Config::Items config, bool for_deleted = false)
: edge_(edge),
edge_type_(edge_type),
from_vertex_(std::move(from_vertex)),
to_vertex_(std::move(to_vertex)),
transaction_(transaction),
indices_(indices),
constraints_(constraints),
config_(config),
for_deleted_(for_deleted) {}
@ -67,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;
@ -91,7 +91,6 @@ class EdgeAccessor final {
VertexId to_vertex_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
// if the accessor was created for a deleted edge.

View File

@ -32,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;
}
@ -266,7 +266,7 @@ 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, VerticesSkipList::Accessor vertices) {
@ -302,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);
}
@ -327,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_->vertex_validator_),
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_(nullptr) {
AdvanceUntilValid();
}
@ -345,22 +345,21 @@ 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_->vertex_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 VertexValidator &vertex_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),
vertex_validator_(&vertex_validator) {}
@ -396,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});
}
}
}
@ -412,7 +411,7 @@ 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});
}
}
}
@ -455,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;
@ -480,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_->vertex_validator_),
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_(nullptr) {
AdvanceUntilValid();
}
@ -519,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_->vertex_validator_);
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->config_,
*self_->vertex_validator_);
break;
}
}
@ -542,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 VertexValidator &vertex_validator)
Transaction *transaction, Indices *indices, Config::Items config,
const VertexValidator &vertex_validator)
: index_accessor_(std::move(index_accessor)),
label_(label),
property_(property),
@ -552,7 +551,6 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
view_(view),
transaction_(transaction),
indices_(indices),
constraints_(constraints),
config_(config),
vertex_validator_(&vertex_validator) {
// We have to fix the bounds that the user provided to us. If the user
@ -698,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) {

View File

@ -28,7 +28,6 @@
namespace memgraph::storage::v3 {
struct Indices;
struct Constraints;
class LabelIndex {
private:
@ -53,8 +52,8 @@ class LabelIndex {
};
public:
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator)
: indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_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);
@ -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 VertexValidator &vertex_validator);
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
class Iterator {
public:
@ -105,7 +104,6 @@ class LabelIndex {
View view_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@ -114,7 +112,7 @@ class LabelIndex {
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_, *vertex_validator_};
return {it->second.access(), label, view, transaction, indices_, config_, *vertex_validator_};
}
int64_t ApproximateVertexCount(LabelId label) {
@ -130,7 +128,6 @@ class LabelIndex {
private:
std::map<LabelId, utils::SkipList<Entry>> index_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@ -150,9 +147,8 @@ class LabelPropertyIndex {
};
public:
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config,
const VertexValidator &vertex_validator)
: indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_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);
@ -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 VertexValidator &vertex_validator);
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
class Iterator {
public:
@ -211,7 +207,6 @@ class LabelPropertyIndex {
View view_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@ -222,8 +217,8 @@ class LabelPropertyIndex {
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_, *vertex_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 VertexValidator *vertex_validator_;
};
struct Indices {
Indices(Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator)
: label_index(this, constraints, config, vertex_validator),
label_property_index(this, constraints, config, vertex_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`

View File

@ -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`
@ -117,7 +118,7 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&..
// TODO(antaljanosbenjamin): clang-tidy detects (in my opinion a false positive) issue in
// `Shard::Accessor::CreateEdge`.
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
delta->next.store(object->delta, std::memory_order_release);
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

View File

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

View File

@ -1,2 +0,0 @@
# autogenerated files
rpc.hpp

View File

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

View File

@ -1,617 +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 //////
Shard::ReplicationClient::ReplicationClient(std::string name, Shard *shard, const io::network::Endpoint &endpoint,
const replication::ReplicationMode mode,
const replication::ReplicationClientConfig &config)
: name_(std::move(name)), shard_(shard), 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 Shard::ReplicationClient::TryInitializeClientAsync() {
thread_pool_.AddTask([this] {
rpc_client_->Abort();
this->TryInitializeClientSync();
});
}
void Shard::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 Shard::ReplicationClient::InitializeClient() {
uint64_t current_commit_timestamp{kTimestampInitialId};
auto stream{rpc_client_->Stream<replication::HeartbeatRpc>(shard_->last_commit_timestamp_, shard_->epoch_id_)};
const auto response = stream.AwaitResponse();
std::optional<uint64_t> branching_point;
if (response.epoch_id != shard_->epoch_id_ && response.current_commit_timestamp != kTimestampInitialId) {
const auto &epoch_history = shard_->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: {}", shard_->last_commit_timestamp_);
if (current_commit_timestamp == shard_->last_commit_timestamp_) {
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 Shard::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 Shard::ReplicationClient::HandleRpcFailure() {
spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication"));
TryInitializeClientAsync();
}
replication::SnapshotRes Shard::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 Shard::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 Shard::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, shard_->last_commit_timestamp_, current_wal_seq_num});
replica_state_.store(replication::ReplicaState::REPLICATING);
} catch (const rpc::RpcFailedException &) {
replica_state_.store(replication::ReplicaState::INVALID);
HandleRpcFailure();
}
return;
}
}
void Shard::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 Shard::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 Shard::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 Shard::ReplicationClient::RecoverReplica(uint64_t replica_commit) {
while (true) {
auto file_locker = shard_->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>) {
if (shard_->wal_file_ && shard_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) {
shard_->wal_file_->DisableFlushing();
spdlog::debug("Sending current wal file");
replica_commit = ReplicateCurrentWal();
shard_->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: {}", shard_->last_commit_timestamp_);
if (shard_->last_commit_timestamp_ == replica_commit) {
replica_state_.store(replication::ReplicaState::READY);
return;
}
}
}
uint64_t Shard::ReplicationClient::ReplicateCurrentWal() {
const auto &wal_file = shard_->wal_file_;
auto stream = TransferCurrentWalFile();
stream.AppendFilename(wal_file->Path().filename());
utils::InputFile file;
MG_ASSERT(file.Open(shard_->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<Shard::ReplicationClient::RecoveryStep> Shard::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 (shard_->wal_file_) {
current_wal_seq_num.emplace(shard_->wal_file_->SequenceNumber());
current_wal_from_timestamp.emplace(shard_->wal_file_->FromTimestamp());
}
auto locker_acc = file_locker->Access();
auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_, current_wal_seq_num);
MG_ASSERT(wal_files, "Wal files could not be loaded");
auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->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 Shard::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 Shard::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 //////
Shard::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_->shard_->epoch_id_);
}
void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex,
uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, self_->shard_->config_.items, delta, vertex,
final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge,
uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, delta, edge, final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeTransactionEnd(&encoder, final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties,
uint64_t timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeOperation(&encoder, &self_->shard_->name_id_mapper_, operation, label, properties, timestamp);
}
replication::AppendDeltasRes Shard::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); }
////// CurrentWalHandler //////
Shard::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self)
: self_(self), stream_(self_->rpc_client_->Stream<replication::CurrentWalRpc>()) {}
void Shard::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteString(filename);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteUint(size);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteFileData(file);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteBuffer(buffer, buffer_size);
}
replication::CurrentWalRes Shard::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); }
} // namespace memgraph::storage::v3

View File

@ -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/shard.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 Shard::ReplicationClient {
public:
ReplicationClient(std::string name, Shard *shard, 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_;
Shard *shard_;
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

View File

@ -1,570 +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
Shard::ReplicationServer::ReplicationServer(Shard *shard, io::network::Endpoint endpoint,
const replication::ReplicationServerConfig &config)
: shard_(shard) {
// 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 Shard::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::HeartbeatReq req;
slk::Load(&req, req_reader);
replication::HeartbeatRes res{true, shard_->last_commit_timestamp_, shard_->epoch_id_};
slk::Save(res, res_builder);
}
void Shard::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 Shard::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 != shard_->epoch_id_) {
shard_->epoch_history_.emplace_back(std::move(shard_->epoch_id_), shard_->last_commit_timestamp_);
shard_->epoch_id_ = std::move(*maybe_epoch_id);
}
if (shard_->wal_file_) {
if (req.seq_num > shard_->wal_file_->SequenceNumber() || *maybe_epoch_id != shard_->epoch_id_) {
shard_->wal_file_->FinalizeWal();
shard_->wal_file_.reset();
shard_->wal_seq_num_ = req.seq_num;
} else {
MG_ASSERT(shard_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file");
shard_->wal_seq_num_ = req.seq_num + 1;
}
} else {
shard_->wal_seq_num_ = req.seq_num;
}
if (req.previous_commit_timestamp != shard_->last_commit_timestamp_) {
// 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, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
return;
}
ReadAndApplyDelta(&decoder);
replication::AppendDeltasRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::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(shard_->snapshot_directory_);
const auto maybe_snapshot_path = decoder.ReadFile(shard_->snapshot_directory_);
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
// Clear the database
shard_->vertices_.clear();
shard_->edges_.clear();
shard_->constraints_ = Constraints();
shard_->indices_.label_index =
LabelIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_);
shard_->indices_.label_property_index =
LabelPropertyIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_);
try {
spdlog::debug("Loading snapshot");
auto recovered_snapshot = durability::RecoveredSnapshot{};
// durability::LoadSnapshot(*maybe_snapshot_path, &shard_->vertices_, &shard_->edges_,
// &shard_->epoch_history_,
// &shard_->name_id_mapper_, &shard_->edge_count_, shard_->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
shard_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid);
shard_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id);
const auto &recovery_info = recovered_snapshot.recovery_info;
shard_->timestamp_ = std::max(shard_->timestamp_, recovery_info.next_timestamp);
// durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &shard_->indices_,
// &shard_->constraints_, &shard_->vertices_);
} catch (const durability::RecoveryFailure &e) {
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
}
replication::SnapshotRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
// Delete other durability files
auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->uuid_);
for (const auto &[path, uuid, _] : snapshot_files) {
if (path != *maybe_snapshot_path) {
shard_->file_retainer_.DeleteFile(path);
}
}
auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_);
if (wal_files) {
for (const auto &wal_file : *wal_files) {
shard_->file_retainer_.DeleteFile(wal_file.path);
}
shard_->wal_file_.reset();
}
}
void Shard::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(shard_->wal_directory_);
for (auto i = 0; i < wal_file_number; ++i) {
LoadWal(&decoder);
}
replication::WalFilesRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::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(shard_->wal_directory_);
LoadWal(&decoder);
replication::CurrentWalRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::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) {
shard_->uuid_ = wal_info.uuid;
}
if (wal_info.epoch_id != shard_->epoch_id_) {
shard_->epoch_history_.emplace_back(wal_info.epoch_id, shard_->last_commit_timestamp_);
shard_->epoch_id_ = std::move(wal_info.epoch_id);
}
if (shard_->wal_file_) {
if (shard_->wal_file_->SequenceNumber() != wal_info.seq_num) {
shard_->wal_file_->FinalizeWal();
shard_->wal_seq_num_ = wal_info.seq_num;
shard_->wal_file_.reset();
}
} else {
shard_->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());
}
}
Shard::ReplicationServer::~ReplicationServer() {
if (rpc_server_) {
rpc_server_->Shutdown();
rpc_server_->AwaitShutdown();
}
}
uint64_t Shard::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) {
auto edge_acc = shard_->edges_.access();
// auto vertex_acc = shard_->vertices_.access();
std::optional<std::pair<uint64_t, Shard::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, shard_->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 = shard_->last_commit_timestamp_;
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 < shard_->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 (!shard_->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.
// {
// auto is_visible = !edge->deleted;
// auto *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_,
// &shard_->indices_,
// &shard_->constraints_,
// shard_->config_.items,
// shard_->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 (!shard_->CreateIndex(shard_->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 (!shard_->DropIndex(shard_->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 (!shard_->CreateIndex(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->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 (!shard_->DropIndex(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->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 = shard_->CreateExistenceConstraint(
// shard_->NameToLabel(delta.operation_label_property.label),
// shard_->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 (!shard_->DropExistenceConstraint(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->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(shard_->NameToProperty(prop));
// }
// auto ret = shard_->CreateUniqueConstraint(shard_->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(shard_->NameToProperty(prop));
// }
// auto ret = shard_->DropUniqueConstraint(shard_->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!");
shard_->last_commit_timestamp_ = max_commit_timestamp;
return applied_deltas;
}
} // namespace memgraph::storage::v3

View File

@ -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/shard.hpp"
namespace memgraph::storage::v3 {
class Shard::ReplicationServer {
public:
explicit ReplicationServer(Shard *shard, 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_;
Shard *shard_;
};
} // namespace memgraph::storage::v3

View File

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

View File

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

View File

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

View File

@ -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(&microseconds, reader);
*value = storage::v3::PropertyValue(storage::v3::TemporalData{temporal_type, microseconds});
return;
}
}
}
} // namespace memgraph::slk

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,6 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <map>
@ -21,13 +20,11 @@
#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/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"
@ -46,6 +43,7 @@
#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"
@ -55,13 +53,6 @@
#include "utils/synchronized.hpp"
#include "utils/uuid.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"
namespace memgraph::storage::v3 {
// The storage is based on this paper:
@ -78,7 +69,6 @@ class AllVerticesIterable final {
Transaction *transaction_;
View view_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
const Schemas *schemas_;
@ -102,13 +92,11 @@ class AllVerticesIterable final {
};
AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view,
Indices *indices, Constraints *constraints, Config::Items config,
const VertexValidator &vertex_validator)
Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
: vertices_accessor_(std::move(vertices_accessor)),
transaction_(transaction),
view_(view),
indices_(indices),
constraints_(constraints),
config_(config),
vertex_validator_{&vertex_validator} {}
@ -184,13 +172,6 @@ struct IndicesInfo {
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;
@ -202,11 +183,8 @@ struct StorageInfo {
uint64_t edge_count;
double average_degree;
uint64_t memory_usage;
uint64_t disk_usage;
};
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
class Shard final {
public:
/// @throw std::system_error
@ -224,19 +202,9 @@ class Shard final {
private:
friend class Shard;
explicit Accessor(Shard *shard, IsolationLevel isolation_level);
Accessor(Shard &shard, Transaction &transaction);
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();
// TODO(gvolfing) this is just a workaround for stitching remove this later.
LabelId GetPrimaryLabel() const noexcept { return shard_->primary_label_; }
@ -253,9 +221,8 @@ class Shard final {
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_->constraints_, shard_->config_.items,
shard_->vertex_validator_));
return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), transaction_, view, &shard_->indices_,
shard_->config_.items, shard_->vertex_validator_));
}
VerticesIterable Vertices(LabelId label, View view);
@ -316,8 +283,16 @@ class Shard final {
/// @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); }
@ -330,56 +305,42 @@ class Shard final {
return {shard_->indices_.label_index.ListIndices(), shard_->indices_.label_property_index.ListIndices()};
}
ConstraintsInfo ListAllConstraints() const {
return {ListExistenceConstraints(shard_->constraints_),
shard_->constraints_.unique_constraints.ListConstraints()};
}
const SchemaValidator &GetSchemaValidator() const;
SchemasInfo ListAllSchemas() const { return {shard_->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 = {});
void Commit(coordinator::Hlc commit_timestamp);
/// @throw std::bad_alloc
void Abort();
void FinalizeTransaction();
private:
/// @throw std::bad_alloc
VertexAccessor CreateVertex(Gid gid, LabelId primary_label);
Shard *shard_;
Transaction transaction_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
Transaction *transaction_;
Config::Items config_;
};
Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) {
return Accessor{this, override_isolation_level.value_or(isolation_level_)};
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 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 = {});
@ -392,45 +353,6 @@ class Shard final {
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;
@ -441,73 +363,15 @@ class Shard final {
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 };
// Might invalidate accessors
void CollectGarbage(io::Time current_time);
utils::BasicResult<CreateSnapshotError> CreateSnapshot();
void StoreMapping(std::unordered_map<uint64_t, std::string> id_to_name);
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);
Transaction &GetTransaction(coordinator::Hlc start_timestamp, IsolationLevel isolation_level);
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
@ -528,45 +392,22 @@ class Shard final {
SchemaValidator schema_validator_;
VertexValidator vertex_validator_;
Constraints constraints_;
Indices indices_;
Schemas schemas_;
// Transaction engine
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_;
std::list<Transaction> committed_transactions_;
std::list<Transaction *> committed_transactions_;
IsolationLevel isolation_level_;
Config config_;
// Undo buffers that were unlinked and now are waiting to be freed.
std::list<std::pair<uint64_t, std::list<Delta>>> garbage_undo_buffers_;
// 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_;
// 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, PrimaryKey>> garbage_vertices_;
// Edges that are logically deleted and wait to be removed from the main
// storage.
std::list<Gid> deleted_edges_;
// Durability
std::filesystem::path snapshot_directory_;
std::filesystem::path wal_directory_;
std::filesystem::path lock_file_path_;
utils::OutputFile lock_file_handle_;
// 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.
@ -592,7 +433,6 @@ class Shard final {
// 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_;
@ -600,27 +440,10 @@ class Shard final {
// Global locker that is used for clients file locking
utils::FileRetainer::FileLocker global_locker_;
// Last commited timestamp
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_;
ReplicationRole replication_role_{ReplicationRole::MAIN};
// 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

View File

@ -0,0 +1,222 @@
// 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 <queue>
#include <set>
#include <boost/uuid/uuid.hpp>
#include <coordinator/coordinator.hpp>
#include <io/address.hpp>
#include <io/message_conversion.hpp>
#include <io/messages.hpp>
#include <io/rsm/raft.hpp>
#include <io/rsm/shard_rsm.hpp>
#include <io/time.hpp>
#include <io/transport.hpp>
namespace memgraph::storage::v3 {
using boost::uuids::uuid;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using memgraph::coordinator::HeartbeatRequest;
using memgraph::coordinator::HeartbeatResponse;
using memgraph::io::Address;
using memgraph::io::Duration;
using memgraph::io::Message;
using memgraph::io::RequestId;
using memgraph::io::ResponseFuture;
using memgraph::io::Time;
using memgraph::io::messages::CoordinatorMessages;
using memgraph::io::messages::ShardManagerMessages;
using memgraph::io::messages::ShardMessages;
using memgraph::io::rsm::Raft;
using memgraph::io::rsm::ShardRsm;
using memgraph::io::rsm::StorageReadRequest;
using memgraph::io::rsm::StorageReadResponse;
using memgraph::io::rsm::StorageWriteRequest;
using memgraph::io::rsm::StorageWriteResponse;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using ShardManagerOrRsmMessage = std::variant<ShardMessages, ShardManagerMessages>;
using TimeUuidPair = std::pair<Time, uuid>;
template <typename IoImpl>
using ShardRaft =
Raft<IoImpl, ShardRsm, StorageWriteRequest, StorageWriteResponse, StorageReadRequest, StorageReadResponse>;
using namespace std::chrono_literals;
static constexpr Duration kMinimumCronInterval = 1000ms;
static constexpr Duration kMaximumCronInterval = 2000ms;
static_assert(kMinimumCronInterval < kMaximumCronInterval,
"The minimum cron interval has to be smaller than the maximum cron interval!");
/// The ShardManager is responsible for:
/// * reconciling the storage engine's local configuration with the Coordinator's
/// intentions for how it should participate in multiple raft clusters
/// * replying to heartbeat requests to the Coordinator
/// * routing incoming messages to the appropriate sRSM
///
/// Every storage engine has exactly one RsmEngine.
template <typename IoImpl>
class ShardManager {
public:
ShardManager(io::Io<IoImpl> io, Address coordinator_leader) : io_(io), coordinator_leader_(coordinator_leader) {}
/// Periodic protocol maintenance. Returns the time that Cron should be called again
/// in the future.
Time Cron() {
spdlog::info("running ShardManager::Cron, address {}", io_.GetAddress().ToString());
Time now = io_.Now();
if (now >= next_cron_) {
Reconciliation();
std::uniform_int_distribution time_distrib(kMinimumCronInterval.count(), kMaximumCronInterval.count());
const auto rand = io_.Rand(time_distrib);
next_cron_ = now + Duration{rand};
}
if (!cron_schedule_.empty()) {
const auto &[time, uuid] = cron_schedule_.top();
if (time <= now) {
auto &rsm = rsm_map_.at(uuid);
Time next_for_uuid = rsm.Cron();
cron_schedule_.pop();
cron_schedule_.push(std::make_pair(next_for_uuid, uuid));
const auto &[next_time, _uuid] = cron_schedule_.top();
return std::min(next_cron_, next_time);
}
}
return next_cron_;
}
/// Returns the Address for our underlying Io implementation
Address GetAddress() { return io_.GetAddress(); }
void Receive(ShardManagerMessages &&smm, RequestId request_id, Address from) {}
void Route(ShardMessages &&sm, RequestId request_id, Address to, Address from) {
Address address = io_.GetAddress();
MG_ASSERT(address.last_known_port == to.last_known_port);
MG_ASSERT(address.last_known_ip == to.last_known_ip);
auto &rsm = rsm_map_.at(to.unique_id);
rsm.Handle(std::forward<ShardMessages>(sm), request_id, from);
}
private:
io::Io<IoImpl> io_;
std::map<uuid, ShardRaft<IoImpl>> rsm_map_;
std::priority_queue<std::pair<Time, uuid>, std::vector<std::pair<Time, uuid>>, std::greater<>> cron_schedule_;
Time next_cron_;
Address coordinator_leader_;
std::optional<ResponseFuture<WriteResponse<CoordinatorWriteResponses>>> heartbeat_res_;
// TODO(tyler) over time remove items from initialized_but_not_confirmed_rsm_
// after the Coordinator is clearly aware of them
std::set<boost::uuids::uuid> initialized_but_not_confirmed_rsm_;
void Reconciliation() {
if (heartbeat_res_.has_value()) {
if (heartbeat_res_->IsReady()) {
io::ResponseResult<WriteResponse<CoordinatorWriteResponses>> response_result =
std::move(heartbeat_res_).value().Wait();
heartbeat_res_.reset();
if (response_result.HasError()) {
spdlog::error("SM timed out while trying to reach C");
} else {
auto response_envelope = response_result.GetValue();
WriteResponse<CoordinatorWriteResponses> wr = response_envelope.message;
if (wr.retry_leader.has_value()) {
spdlog::info("SM redirected to new C leader");
coordinator_leader_ = wr.retry_leader.value();
} else if (wr.success) {
CoordinatorWriteResponses cwr = wr.write_return;
HeartbeatResponse hr = std::get<HeartbeatResponse>(cwr);
spdlog::info("SM received heartbeat response from C");
EnsureShardsInitialized(hr);
}
}
} else {
return;
}
}
HeartbeatRequest req{
.from_storage_manager = GetAddress(),
.initialized_rsms = initialized_but_not_confirmed_rsm_,
};
CoordinatorWriteRequests cwr = req;
WriteRequest<CoordinatorWriteRequests> ww;
ww.operation = cwr;
spdlog::info("SM sending heartbeat to coordinator {}", coordinator_leader_.ToString());
heartbeat_res_.emplace(std::move(
io_.template Request<WriteRequest<CoordinatorWriteRequests>, WriteResponse<CoordinatorWriteResponses>>(
coordinator_leader_, ww)));
spdlog::info("SM sent heartbeat");
}
void EnsureShardsInitialized(HeartbeatResponse hr) {
for (const auto &shard_to_initialize : hr.shards_to_initialize) {
InitializeRsm(shard_to_initialize);
initialized_but_not_confirmed_rsm_.emplace(shard_to_initialize.uuid);
}
}
/// Returns true if the RSM was able to be initialized, and false if it was already initialized
void InitializeRsm(coordinator::ShardToInitialize to_init) {
if (rsm_map_.contains(to_init.uuid)) {
// it's not a bug for the coordinator to send us UUIDs that we have
// already created, because there may have been lag that caused
// the coordinator not to hear back from us.
return;
}
auto rsm_io = io_.ForkLocal();
auto io_addr = rsm_io.GetAddress();
io_addr.unique_id = to_init.uuid;
rsm_io.SetAddress(io_addr);
// TODO(tyler) get geers from Coordinator in HeartbeatResponse
std::vector<Address> rsm_peers = {};
// TODO(everbody) change this to storage::Shard
ShardRsm rsm_state{};
ShardRaft<IoImpl> rsm{std::move(rsm_io), rsm_peers, std::move(rsm_state)};
spdlog::info("SM created a new shard with UUID {}", to_init.uuid);
rsm_map_.emplace(to_init.uuid, std::move(rsm));
}
};
} // namespace memgraph::storage::v3

View File

@ -12,6 +12,7 @@
#include <iterator>
#include <utility>
#include "query/v2/requests.hpp"
#include "storage/v3/shard_rsm.hpp"
#include "storage/v3/value_conversions.hpp"
#include "storage/v3/vertex_accessor.hpp"
@ -113,7 +114,7 @@ Value ConstructValueVertex(const memgraph::storage::v3::VertexAccessor &acc, mem
namespace memgraph::storage::v3 {
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
auto acc = shard_->Access();
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.
@ -163,23 +164,12 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
}
}
msgs::CreateVerticesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(&"ConstraintViolation, commiting vertices was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
return msgs::CreateVerticesResponse{action_successful};
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
bool action_successful = true;
auto acc = shard_->Access();
auto acc = shard_->Access(req.transaction_id);
for (auto &propval : req.primary_keys) {
if (!action_successful) {
@ -189,9 +179,8 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
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]);
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)
@ -202,7 +191,7 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
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]);
spdlog::debug("Error while trying to delete vertex. Transaction id: {}", req.transaction_id.logical_id);
}
break;
@ -211,8 +200,8 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
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]);
spdlog::debug("Error while trying to detach and delete vertex. Transaction id: {}",
req.transaction_id.logical_id);
}
break;
@ -221,23 +210,11 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
}
}
msgs::DeleteVerticesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(&"ConstraintViolation, commiting vertices was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
return msgs::DeleteVerticesResponse{action_successful};
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
auto acc = shard_->Access();
auto acc = shard_->Access(req.transaction_id);
bool action_successful = true;
for (auto &edge : req.edges) {
@ -249,8 +226,8 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
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]);
spdlog::debug("Error while trying to insert edge, vertex does not exist. Transaction id: {}",
req.transaction_id.logical_id);
break;
}
@ -261,30 +238,16 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
if (edge_acc.HasError()) {
action_successful = false;
spdlog::debug(&"Creating edge was not successful. Transaction id: "[req.transaction_id.logical_id]);
spdlog::debug("Creating edge was not successful. Transaction id: {}", req.transaction_id.logical_id);
break;
}
}
msgs::CreateEdgesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(
&"ConstraintViolation, commiting edge creation was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
return msgs::CreateEdgesResponse{action_successful};
}
msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
auto acc = shard_->Access();
auto acc = shard_->Access(req.transaction_id);
bool action_successful = true;
std::vector<msgs::ScanResultRow> results;
@ -343,6 +306,11 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
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{};

View File

@ -14,7 +14,6 @@
#include <memory>
#include <variant>
#include <openssl/ec.h>
#include "query/v2/requests.hpp"
#include "storage/v3/shard.hpp"
#include "storage/v3/vertex_accessor.hpp"
@ -39,6 +38,8 @@ class ShardRsm {
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)){};

View File

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

View File

@ -13,6 +13,7 @@
#include <memory>
#include "storage/v3/conversions.hpp"
#include "storage/v3/edge_accessor.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/indices.hpp"
@ -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 VertexValidator &vertex_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, vertex_validator};
return VertexAccessor{vertex, transaction, indices, config, vertex_validator};
}
bool VertexAccessor::IsVisible(View view) const {
@ -150,6 +151,8 @@ 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;
@ -371,6 +374,10 @@ 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;
@ -537,7 +544,7 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::
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, id, transaction_, indices_, constraints_, config_);
ret.emplace_back(edge, edge_type, from_vertex, id, transaction_, indices_, config_);
}
return ret;
}
@ -617,7 +624,7 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std:
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, id, to_vertex, transaction_, indices_, constraints_, config_);
ret.emplace_back(edge, edge_type, id, to_vertex, transaction_, indices_, config_);
}
return ret;
}

View File

@ -28,7 +28,6 @@ namespace memgraph::storage::v3 {
class EdgeAccessor;
class Shard;
struct Indices;
struct Constraints;
class VertexAccessor final {
private:
@ -37,19 +36,17 @@ class VertexAccessor final {
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 VertexValidator &vertex_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_{&vertex_validator},
for_deleted_(for_deleted) {}
static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
Constraints *constraints, Config::Items config,
const VertexValidator &vertex_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;
@ -66,6 +63,8 @@ class VertexAccessor final {
/// @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
@ -90,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;
@ -136,7 +138,6 @@ class VertexAccessor final {
Vertex *vertex_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;

View File

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

View File

@ -11,6 +11,7 @@ function(add_simulation_test test_cpp)
get_filename_component(exec_name ${test_cpp} NAME_WE)
set(target_name ${test_prefix}${exec_name})
add_executable(${target_name} ${test_cpp})
# OUTPUT_NAME sets the real name of a target when it is built and can be
# used to help create two targets of the same name even though CMake
# requires unique logical target names
@ -20,7 +21,8 @@ function(add_simulation_test test_cpp)
target_compile_options(${target_name} PRIVATE -fsanitize=${san})
target_link_options(${target_name} PRIVATE -fsanitize=${san})
target_link_libraries(${target_name} mg-storage-v3 mg-communication gtest gmock mg-utils mg-io mg-io-simulator Boost::headers mg-query-v2)
target_link_libraries(${target_name} mg-storage-v3 mg-communication gtest gmock mg-utils mg-io mg-io-simulator mg-coordinator Boost::headers mg-query-v2)
# register test
add_test(${target_name} ${exec_name})
add_dependencies(memgraph__simulation ${target_name})

View File

@ -37,7 +37,7 @@
#include "utils/result.hpp"
using memgraph::coordinator::AddressAndStatus;
using memgraph::coordinator::CompoundKey;
using CompoundKey = memgraph::coordinator::PrimaryKey;
using memgraph::coordinator::Coordinator;
using memgraph::coordinator::CoordinatorClient;
using memgraph::coordinator::CoordinatorRsm;
@ -98,7 +98,7 @@ ShardMap CreateDummyShardmap(memgraph::coordinator::Address a_io_1, memgraph::co
SchemaProperty{.property_id = property_id_2, .type = type_2},
};
bool label_success = sm.InitializeNewLabel(label_name, schema, sm.shard_map_version);
auto label_success = sm.InitializeNewLabel(label_name, schema, 1, sm.shard_map_version);
MG_ASSERT(label_success);
const LabelId label_id = sm.labels.at(label_name);

View File

@ -9,6 +9,7 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <chrono>
#include <iostream>
#include <optional>
#include <thread>
@ -126,17 +127,15 @@ bool AttemtpToCreateVertex(ShardClient &client, int64_t value) {
create_req.new_vertices = {vertex};
create_req.transaction_id.logical_id = GetTransactionId();
while (true) {
auto write_res = client.SendWriteRequest(create_req);
if (write_res.HasError()) {
continue;
}
auto write_res = client.SendWriteRequest(create_req);
MG_ASSERT(write_res.HasValue() && std::get<msgs::CreateVerticesResponse>(write_res.GetValue()).success,
"Unexpected failure");
auto write_response_result = write_res.GetValue();
auto write_response = std::get<msgs::CreateVerticesResponse>(write_response_result);
return write_response.success;
}
auto commit_req = msgs::CommitRequest{create_req.transaction_id, msgs::Hlc{.logical_id = GetTransactionId()}};
auto commit_res = client.SendWriteRequest(commit_req);
MG_ASSERT(commit_res.HasValue() && std::get<msgs::CommitResponse>(commit_res.GetValue()).success,
"Unexpected failure");
return true;
}
bool AttemptToAddEdge(ShardClient &client, int64_t value_of_vertex_1, int64_t value_of_vertex_2, int64_t edge_gid,
@ -161,17 +160,15 @@ bool AttemptToAddEdge(ShardClient &client, int64_t value_of_vertex_1, int64_t va
create_req.edges = {edge};
create_req.transaction_id.logical_id = GetTransactionId();
while (true) {
auto write_res = client.SendWriteRequest(create_req);
if (write_res.HasError()) {
continue;
}
auto write_res = client.SendWriteRequest(create_req);
MG_ASSERT(write_res.HasValue() && std::get<msgs::CreateEdgesResponse>(write_res.GetValue()).success,
"Unexpected failure");
auto write_response_result = write_res.GetValue();
auto write_response = std::get<msgs::CreateEdgesResponse>(write_response_result);
return write_response.success;
}
auto commit_req = msgs::CommitRequest{create_req.transaction_id, msgs::Hlc{.logical_id = GetTransactionId()}};
auto commit_res = client.SendWriteRequest(commit_req);
MG_ASSERT(commit_res.HasValue() && std::get<msgs::CommitResponse>(commit_res.GetValue()).success,
"Unexpected failure");
return true;
}
std::tuple<size_t, std::optional<msgs::VertexId>> AttemptToScanAllWithBatchLimit(ShardClient &client,
@ -298,12 +295,16 @@ int TestMessages() {
auto simulator = Simulator(config);
Io<SimulatorTransport> shard_server_io_1 = simulator.RegisterNew();
shard_server_io_1.SetDefaultTimeout(std::chrono::seconds(1));
const auto shard_server_1_address = shard_server_io_1.GetAddress();
Io<SimulatorTransport> shard_server_io_2 = simulator.RegisterNew();
shard_server_io_2.SetDefaultTimeout(std::chrono::seconds(1));
const auto shard_server_2_address = shard_server_io_2.GetAddress();
Io<SimulatorTransport> shard_server_io_3 = simulator.RegisterNew();
shard_server_io_3.SetDefaultTimeout(std::chrono::seconds(1));
const auto shard_server_3_address = shard_server_io_3.GetAddress();
Io<SimulatorTransport> shard_client_io = simulator.RegisterNew();
shard_client_io.SetDefaultTimeout(std::chrono::seconds(1));
PropertyValue min_pk(static_cast<int64_t>(0));
std::vector<PropertyValue> min_prim_key = {min_pk};

View File

@ -32,13 +32,14 @@
#include "storage/v3/schemas.hpp"
#include "utils/result.hpp"
using memgraph::common::SchemaType;
using memgraph::coordinator::AddressAndStatus;
using memgraph::coordinator::CompoundKey;
using memgraph::coordinator::Coordinator;
using memgraph::coordinator::CoordinatorClient;
using memgraph::coordinator::CoordinatorRsm;
using memgraph::coordinator::HlcRequest;
using memgraph::coordinator::HlcResponse;
using memgraph::coordinator::PrimaryKey;
using memgraph::coordinator::Shard;
using memgraph::coordinator::ShardMap;
using memgraph::coordinator::Shards;
@ -65,9 +66,11 @@ using memgraph::io::simulator::SimulatorConfig;
using memgraph::io::simulator::SimulatorStats;
using memgraph::io::simulator::SimulatorTransport;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::PropertyValue;
using memgraph::storage::v3::SchemaProperty;
using memgraph::utils::BasicResult;
using PrimaryKey = std::vector<PropertyValue>;
using ShardClient =
RsmClient<SimulatorTransport, StorageWriteRequest, StorageWriteResponse, StorageReadRequest, StorageReadResponse>;
namespace {
@ -83,18 +86,20 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
const auto properties = sm.AllocatePropertyIds(property_names);
const auto property_id_1 = properties.at("property_1");
const auto property_id_2 = properties.at("property_2");
const auto type_1 = memgraph::common::SchemaType::INT;
const auto type_2 = memgraph::common::SchemaType::INT;
const auto type_1 = SchemaType::INT;
const auto type_2 = SchemaType::INT;
// register new label space
std::vector<SchemaProperty> schema = {
SchemaProperty{.property_id = property_id_1, .type = type_1},
SchemaProperty{.property_id = property_id_2, .type = type_2},
};
bool label_success = sm.InitializeNewLabel(label_name, schema, sm.shard_map_version);
MG_ASSERT(label_success);
size_t replication_factor = 3;
std::optional<LabelId> label_id_opt =
sm.InitializeNewLabel(label_name, schema, replication_factor, sm.shard_map_version);
MG_ASSERT(label_id_opt.has_value());
const LabelId label_id = sm.labels.at(label_name);
const LabelId label_id = label_id_opt.value();
auto &label_space = sm.label_spaces.at(label_id);
Shards &shards_for_label = label_space.shards;
@ -105,9 +110,9 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
Shard shard1 = {aas1_1, aas1_2, aas1_3};
const auto key1 = memgraph::storage::v3::PropertyValue(0);
const auto key2 = memgraph::storage::v3::PropertyValue(0);
const CompoundKey compound_key_1 = {key1, key2};
const auto key1 = PropertyValue(0);
const auto key2 = PropertyValue(0);
const PrimaryKey compound_key_1 = {key1, key2};
shards_for_label.emplace(compound_key_1, shard1);
// add second shard at [12, 13]
@ -117,9 +122,9 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
Shard shard2 = {aas2_1, aas2_2, aas2_3};
auto key3 = memgraph::storage::v3::PropertyValue(12);
auto key4 = memgraph::storage::v3::PropertyValue(13);
CompoundKey compound_key_2 = {key3, key4};
auto key3 = PropertyValue(12);
auto key4 = PropertyValue(13);
PrimaryKey compound_key_2 = {key3, key4};
shards_for_label[compound_key_2] = shard2;
return sm;
@ -263,11 +268,11 @@ int main() {
req.last_shard_map_version = client_shard_map.GetHlc();
while (true) {
// Create CompoundKey
const auto cm_key_1 = memgraph::storage::v3::PropertyValue(3);
const auto cm_key_2 = memgraph::storage::v3::PropertyValue(4);
// Create PrimaryKey
const auto cm_key_1 = PropertyValue(3);
const auto cm_key_2 = PropertyValue(4);
const CompoundKey compound_key = {cm_key_1, cm_key_2};
const PrimaryKey compound_key = {cm_key_1, cm_key_2};
// Look for Shard
BasicResult<TimedOut, memgraph::coordinator::CoordinatorWriteResponses> read_res =

View File

@ -325,6 +325,9 @@ target_link_libraries(storage_v3_test_utils mg-storage-v3)
add_unit_test(storage_v3.cpp)
target_link_libraries(${test_prefix}storage_v3 mg-storage-v3 storage_v3_test_utils)
add_unit_test(storage_v3_expr.cpp)
target_link_libraries(${test_prefix}storage_v3_expr mg-storage-v3 mg-expr)
add_unit_test(storage_v3_schema.cpp)
target_link_libraries(${test_prefix}storage_v3_schema mg-storage-v3)
@ -378,6 +381,9 @@ target_link_libraries(${test_prefix}storage_v3_vertex_accessors mg-storage-v3)
add_unit_test(storage_v3_edge.cpp)
target_link_libraries(${test_prefix}storage_v3_edge mg-storage-v3)
add_unit_test(storage_v3_isolation_level.cpp)
target_link_libraries(${test_prefix}storage_v3_isolation_level mg-storage-v3)
add_unit_test(replication_persistence_helper.cpp)
target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2)
@ -416,10 +422,14 @@ add_custom_target(test_lcp ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_lcp)
add_test(test_lcp ${CMAKE_CURRENT_BINARY_DIR}/test_lcp)
add_dependencies(memgraph__unit test_lcp)
# Test future
# Test Future
add_unit_test(future.cpp)
target_link_libraries(${test_prefix}future mg-io)
# Test Local Transport
# Test LocalTransport
add_unit_test(local_transport.cpp)
target_link_libraries(${test_prefix}local_transport mg-io)
# Test MachineManager with LocalTransport
add_unit_test(machine_manager.cpp)
target_link_libraries(${test_prefix}machine_manager mg-io mg-coordinator mg-storage-v3)

View File

@ -0,0 +1,209 @@
// 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 <chrono>
#include <limits>
#include <thread>
#include <gtest/gtest.h>
#include <coordinator/coordinator.hpp>
#include <coordinator/coordinator_client.hpp>
#include <coordinator/hybrid_logical_clock.hpp>
#include <coordinator/shard_map.hpp>
#include <io/local_transport/local_system.hpp>
#include <io/local_transport/local_transport.hpp>
#include <io/rsm/rsm_client.hpp>
#include <io/transport.hpp>
#include <machine_manager/machine_config.hpp>
#include <machine_manager/machine_manager.hpp>
#include "io/rsm/rsm_client.hpp"
#include "io/rsm/shard_rsm.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/schemas.hpp"
namespace memgraph::io::tests {
using memgraph::coordinator::Coordinator;
using memgraph::coordinator::CoordinatorClient;
using memgraph::coordinator::CoordinatorReadRequests;
using memgraph::coordinator::CoordinatorReadResponses;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using memgraph::coordinator::Hlc;
using memgraph::coordinator::HlcResponse;
using memgraph::coordinator::Shard;
using memgraph::coordinator::ShardMap;
using memgraph::io::Io;
using memgraph::io::local_transport::LocalSystem;
using memgraph::io::local_transport::LocalTransport;
using memgraph::io::rsm::RsmClient;
using memgraph::io::rsm::StorageReadRequest;
using memgraph::io::rsm::StorageReadResponse;
using memgraph::io::rsm::StorageWriteRequest;
using memgraph::io::rsm::StorageWriteResponse;
using memgraph::machine_manager::MachineConfig;
using memgraph::machine_manager::MachineManager;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::SchemaProperty;
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
using ShardClient =
RsmClient<LocalTransport, StorageWriteRequest, StorageWriteResponse, StorageReadRequest, StorageReadResponse>;
ShardMap TestShardMap() {
ShardMap sm{};
const std::string label_name = std::string("test_label");
// register new properties
const std::vector<std::string> property_names = {"property_1", "property_2"};
const auto properties = sm.AllocatePropertyIds(property_names);
const auto property_id_1 = properties.at("property_1");
const auto property_id_2 = properties.at("property_2");
const auto type_1 = memgraph::common::SchemaType::INT;
const auto type_2 = memgraph::common::SchemaType::INT;
// register new label space
std::vector<SchemaProperty> schema = {
SchemaProperty{.property_id = property_id_1, .type = type_1},
SchemaProperty{.property_id = property_id_2, .type = type_2},
};
const size_t replication_factor = 1;
std::optional<LabelId> label_id = sm.InitializeNewLabel(label_name, schema, replication_factor, sm.shard_map_version);
MG_ASSERT(label_id);
// split the shard at N split points
// NB: this is the logic that should be provided by the "split file"
// TODO(tyler) split points should account for signedness
const size_t n_splits = 16;
const auto split_interval = std::numeric_limits<int64_t>::max() / n_splits;
for (int64_t i = 0; i < n_splits; ++i) {
const int64_t value = i * split_interval;
const auto key1 = memgraph::storage::v3::PropertyValue(value);
const auto key2 = memgraph::storage::v3::PropertyValue(0);
const CompoundKey split_point = {key1, key2};
const bool split_success = sm.SplitShard(sm.shard_map_version, label_id.value(), split_point);
MG_ASSERT(split_success);
}
return sm;
}
MachineManager<LocalTransport> MkMm(LocalSystem &local_system, std::vector<Address> coordinator_addresses, Address addr,
ShardMap shard_map) {
MachineConfig config{
.coordinator_addresses = coordinator_addresses,
.is_storage = true,
.is_coordinator = true,
.listen_ip = addr.last_known_ip,
.listen_port = addr.last_known_port,
};
Io<LocalTransport> io = local_system.Register(addr);
Coordinator coordinator{shard_map};
return MachineManager{io, config, coordinator};
}
void RunMachine(MachineManager<LocalTransport> mm) { mm.Run(); }
void WaitForShardsToInitialize(CoordinatorClient<LocalTransport> &cc) {
// TODO(tyler) call coordinator client's read method for GetShardMap
// and keep reading it until the shard map contains proper replicas
// for each shard in the label space.
}
TEST(MachineManager, BasicFunctionality) {
LocalSystem local_system;
auto cli_addr = Address::TestAddress(1);
auto machine_1_addr = cli_addr.ForkUniqueAddress();
Io<LocalTransport> cli_io = local_system.Register(cli_addr);
auto coordinator_addresses = std::vector{
machine_1_addr,
};
ShardMap initialization_sm = TestShardMap();
auto mm_1 = MkMm(local_system, coordinator_addresses, machine_1_addr, initialization_sm);
Address coordinator_address = mm_1.CoordinatorAddress();
auto mm_thread_1 = std::jthread(RunMachine, std::move(mm_1));
// TODO(tyler) clarify addresses of coordinator etc... as it's a mess
CoordinatorClient<LocalTransport> cc{cli_io, coordinator_address, {coordinator_address}};
WaitForShardsToInitialize(cc);
using namespace std::chrono_literals;
std::this_thread::sleep_for(2010ms);
// get ShardMap from coordinator
memgraph::coordinator::HlcRequest req;
req.last_shard_map_version = Hlc{
.logical_id = 0,
};
BasicResult<TimedOut, memgraph::coordinator::CoordinatorWriteResponses> read_res = cc.SendWriteRequest(req);
MG_ASSERT(!read_res.HasError(), "HLC request unexpectedly timed out");
auto coordinator_read_response = read_res.GetValue();
HlcResponse hlc_response = std::get<HlcResponse>(coordinator_read_response);
ShardMap sm = hlc_response.fresher_shard_map.value();
// Get shard for key and create rsm client
const auto cm_key_1 = memgraph::storage::v3::PropertyValue(3);
const auto cm_key_2 = memgraph::storage::v3::PropertyValue(4);
const CompoundKey compound_key = {cm_key_1, cm_key_2};
std::string label_name = "test_label";
Shard shard_for_key = sm.GetShardForKey(label_name, compound_key);
auto shard_for_client = std::vector<Address>{};
for (const auto &aas : shard_for_key) {
spdlog::info("got address for shard: {}", aas.address.ToString());
shard_for_client.push_back(aas.address);
}
ShardClient shard_client{cli_io, shard_for_client[0], shard_for_client};
// submit a read request and assert that the requested key does not yet exist
LabelId label_id = sm.labels.at(label_name);
StorageReadRequest storage_get_req;
storage_get_req.label_id = label_id;
storage_get_req.key = compound_key;
storage_get_req.transaction_id = hlc_response.new_hlc;
auto get_response_result = shard_client.SendReadRequest(storage_get_req);
auto get_response = get_response_result.GetValue();
auto val = get_response.value;
MG_ASSERT(!val.has_value());
local_system.ShutDown();
};
} // namespace memgraph::io::tests

View File

@ -27,9 +27,6 @@
#include "query/v2/plan/operator.hpp"
#include "query_v2_query_plan_common.hpp"
#include "storage/v3/conversions.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
class Dummy : public testing::Test {
protected:

View File

@ -591,8 +591,10 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :A(a INTEGER);"));
// // Empty property list should result with syntax exception.
// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException);
// ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException);
// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"),
// memgraph::frontend::opencypher::SyntaxException);
// ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"),
// memgraph::frontend::opencypher::SyntaxException);
// // Too large list of properties should also result with syntax exception.
// {
@ -750,7 +752,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U);
// auto stream =
// Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}});
// Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id",
// memgraph::storage::v3::PropertyValue(42)}});
// ASSERT_EQ(stream.GetHeader().size(), 1U);
// EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN");
// std::vector<std::string> expected_rows{" * Produce {n}", " * Filter", " * ScanAll (n)", " * Once"};
@ -765,9 +768,10 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U);
// // We should have AST cache for EXPLAIN ... and for inner MATCH ...
// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U);
// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something
// else")}}); EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); EXPECT_EQ(interpreter_context.ast_cache.size(),
// 2U);
// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id",
// memgraph::storage::v3::PropertyValue("something else")}});
// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U);
// }
// TEST_F(InterpreterTest, ProfileQuery) {
@ -776,12 +780,10 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U);
// auto stream = Interpret("PROFILE MATCH (n) RETURN *;");
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
// EXPECT_EQ(stream.GetHeader(), expected_header);
// std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"};
// ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
// auto expected_it = expected_rows.begin();
// for (const auto &row : stream.GetResults()) {
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE
// TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"*
// Produce", "* ScanAll", "* Once"}; ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); auto
// expected_it = expected_rows.begin(); for (const auto &row : stream.GetResults()) {
// ASSERT_EQ(row.size(), 4U);
// EXPECT_EQ(row.front().ValueString(), *expected_it);
// ++expected_it;
@ -801,8 +803,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U);
// auto [stream, qid] = Prepare("PROFILE MATCH (n) RETURN *;");
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
// EXPECT_EQ(stream.GetHeader(), expected_header);
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE
// TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header);
// std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"};
// auto expected_it = expected_rows.begin();
@ -835,8 +837,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// TEST_F(InterpreterTest, ProfileQueryInMulticommandTransaction) {
// Interpret("BEGIN");
// ASSERT_THROW(Interpret("PROFILE MATCH (n) RETURN *;"), memgraph::query::v2::ProfileInMulticommandTxException);
// Interpret("ROLLBACK");
// ASSERT_THROW(Interpret("PROFILE MATCH (n) RETURN *;"),
// memgraph::query::v2::ProfileInMulticommandTxException); Interpret("ROLLBACK");
// }
// TEST_F(InterpreterTest, ProfileQueryWithParams) {
@ -845,13 +847,13 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U);
// auto stream =
// Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}});
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
// EXPECT_EQ(stream.GetHeader(), expected_header);
// std::vector<std::string> expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"};
// ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
// auto expected_it = expected_rows.begin();
// for (const auto &row : stream.GetResults()) {
// Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id",
// memgraph::storage::v3::PropertyValue(42)}});
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE
// TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"*
// Produce", "* Filter", "* ScanAll", "* Once"}; ASSERT_EQ(stream.GetResults().size(),
// expected_rows.size()); auto expected_it = expected_rows.begin(); for (const auto &row :
// stream.GetResults()) {
// ASSERT_EQ(row.size(), 4U);
// EXPECT_EQ(row.front().ValueString(), *expected_it);
// ++expected_it;
@ -860,9 +862,10 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U);
// // We should have AST cache for PROFILE ... and for inner MATCH ...
// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U);
// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something
// else")}}); EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); EXPECT_EQ(interpreter_context.ast_cache.size(),
// 2U);
// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id",
// memgraph::storage::v3::PropertyValue("something else")}});
// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U);
// }
// TEST_F(InterpreterTest, ProfileQueryWithLiterals) {
@ -872,10 +875,9 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U);
// EXPECT_EQ(interpreter_context.ast_cache.size(), 1U);
// auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {});
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
// EXPECT_EQ(stream.GetHeader(), expected_header);
// std::vector<std::string> expected_rows{"* CreateNode", "* Unwind", "* Once"};
// ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
// std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE
// TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"*
// CreateNode", "* Unwind", "* Once"}; ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
// auto expected_it = expected_rows.begin();
// for (const auto &row : stream.GetResults()) {
// ASSERT_EQ(row.size(), 4U);
@ -894,11 +896,12 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// TEST_F(InterpreterTest, Transactions) {
// auto &interpreter = default_interpreter.interpreter;
// {
// ASSERT_THROW(interpreter.CommitTransaction(), memgraph::query::v2::ExplicitTransactionUsageException);
// ASSERT_THROW(interpreter.RollbackTransaction(), memgraph::query::v2::ExplicitTransactionUsageException);
// interpreter.BeginTransaction();
// ASSERT_THROW(interpreter.BeginTransaction(), memgraph::query::v2::ExplicitTransactionUsageException);
// auto [stream, qid] = Prepare("RETURN 2");
// ASSERT_THROW(interpreter.CommitTransaction(),
// memgraph::query::v2::ExplicitTransactionUsageException);
// ASSERT_THROW(interpreter.RollbackTransaction(),
// memgraph::query::v2::ExplicitTransactionUsageException); interpreter.BeginTransaction();
// ASSERT_THROW(interpreter.BeginTransaction(),
// memgraph::query::v2::ExplicitTransactionUsageException); auto [stream, qid] = Prepare("RETURN 2");
// ASSERT_EQ(stream.GetHeader().size(), 1U);
// EXPECT_EQ(stream.GetHeader()[0], "2");
// Pull(&stream, 1);
@ -1072,8 +1075,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// writer.Close();
// {
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN
// x.A)",
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}"
// AS x RETURN x.A)",
// csv_path.string(), delimiter);
// auto [stream, qid] = Prepare(query);
// ASSERT_EQ(stream.GetHeader().size(), 1U);
@ -1093,8 +1096,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// }
// {
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN
// x.C)",
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}"
// AS x RETURN x.C)",
// csv_path.string(), delimiter);
// auto [stream, qid] = Prepare(query);
// ASSERT_EQ(stream.GetHeader().size(), 1U);
@ -1140,8 +1143,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// const std::array<std::string, 2> queries = {
// fmt::format("LOAD CSV FROM \"{}\" WITH HEADER AS row RETURN row", csv_path.string()),
// "CREATE TRIGGER trigger ON CREATE BEFORE COMMIT EXECUTE LOAD CSV FROM 'file.csv' WITH HEADER AS row RETURN "
// "row"};
// "CREATE TRIGGER trigger ON CREATE BEFORE COMMIT EXECUTE LOAD CSV FROM 'file.csv' WITH HEADER
// AS row RETURN " "row"};
// InterpreterFaker interpreter_faker{&db_, {.query = {.allow_load_csv = allow_load_csv}},
// directory_manager.Path()}; for (const auto &query : queries) {
@ -1164,8 +1167,9 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// void AssertAllValuesAreZero(const std::map<std::string, memgraph::communication::bolt::Value> &map,
// const std::vector<std::string> &exceptions) {
// for (const auto &[key, value] : map) {
// if (const auto it = std::find(exceptions.begin(), exceptions.end(), key); it != exceptions.end()) continue;
// ASSERT_EQ(value.ValueInt(), 0) << "Value " << key << " actual: " << value.ValueInt() << ", expected 0";
// if (const auto it = std::find(exceptions.begin(), exceptions.end(), key); it != exceptions.end())
// continue; ASSERT_EQ(value.ValueInt(), 0) << "Value " << key << " actual: " << value.ValueInt() <<
// ", expected 0";
// }
// }
@ -1178,7 +1182,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// }
// {
// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)"));
// std::array stats_keys{"nodes-created", "nodes-deleted", "relationships-created", "relationships-deleted",
// std::array stats_keys{"nodes-created", "nodes-deleted", "relationships-created",
// "relationships-deleted",
// "properties-set", "labels-added", "labels-removed"};
// auto [stream, qid] = Prepare("CREATE (:L1 {name: 'name1'});");
// Pull(&stream);
@ -1187,7 +1192,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// ASSERT_TRUE(stream.GetSummary().at("stats").IsMap());
// auto stats = stream.GetSummary().at("stats").ValueMap();
// ASSERT_TRUE(
// std::all_of(stats_keys.begin(), stats_keys.end(), [&stats](const auto &key) { return stats.contains(key);
// std::all_of(stats_keys.begin(), stats_keys.end(), [&stats](const auto &key) { return
// stats.contains(key);
// }));
// AssertAllValuesAreZero(stats, {"nodes-created"});
// }
@ -1197,7 +1203,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)"));
// {
// auto [stream, qid] =
// Prepare("CREATE (:L1{name: 'name1'}),(:L1{name: 'name2'}),(:L1{name: 'name3'}),(:L1{name: 'name4'});");
// Prepare("CREATE (:L1{name: 'name1'}),(:L1{name: 'name2'}),(:L1{name: 'name3'}),(:L1{name:
// 'name4'});");
// Pull(&stream);
// auto stats = stream.GetSummary().at("stats").ValueMap();
@ -1214,7 +1221,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// }
// {
// auto [stream, qid] =
// Prepare("CREATE (n:L1 {name: 'name5'})-[:TO]->(m:L1{name: 'name6'}), (n)-[:TO]->(m), (n)-[:TO]->(m);");
// Prepare("CREATE (n:L1 {name: 'name5'})-[:TO]->(m:L1{name: 'name6'}), (n)-[:TO]->(m),
// (n)-[:TO]->(m);");
// Pull(&stream);
@ -1319,8 +1327,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "IndexAlreadyExists");
// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id already exists.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id already
// exists."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// {
// auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);");
@ -1345,8 +1353,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "IndexDoesNotExist");
// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id doesn't exist.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id doesn't
// exist."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// }
@ -1420,8 +1428,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "CreateConstraint");
// ASSERT_EQ(notification["title"].ValueString(), "Created EXISTS constraint on label L1 on properties name.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Created EXISTS constraint on label L1 on
// properties name."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// {
// auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);");
@ -1433,8 +1441,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "ConstraintAlreadyExists");
// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name already
// exists."); ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name
// already exists."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// {
// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);");
@ -1446,8 +1454,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "DropConstraint");
// ASSERT_EQ(notification["title"].ValueString(), "Dropped EXISTS constraint on label L1 on properties name.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Dropped EXISTS constraint on label L1 on
// properties name."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// {
// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);");
@ -1459,8 +1467,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// auto notification = notifications[0].ValueMap();
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist");
// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name doesn'texist.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name
// doesn'texist."); ASSERT_EQ(notification["description"].ValueString(), "");
// }
// }
@ -1510,7 +1518,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// writer.Close();
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x;)",
// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS
// x RETURN x;)",
// csv_path.string(), delimiter);
// auto [stream, qid] = Prepare(query);
// Pull(&stream);
@ -1522,9 +1531,9 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// ASSERT_EQ(notification["severity"].ValueString(), "INFO");
// ASSERT_EQ(notification["code"].ValueString(), "LoadCSVTip");
// ASSERT_EQ(notification["title"].ValueString(),
// "It's important to note that the parser parses the values as strings. It's up to the user to "
// "convert the parsed row values to the appropriate type. This can be done using the built-in "
// "conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
// "It's important to note that the parser parses the values as strings. It's up to the user
// to " "convert the parsed row values to the appropriate type. This can be done using the
// built-in " "conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
// ASSERT_EQ(notification["description"].ValueString(), "");
// }
@ -1543,30 +1552,32 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) {
// Interpret("BEGIN");
// ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException);
// Interpret("ROLLBACK");
// ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"),
// memgraph::query::v2::ConstraintInMulticommandTxException); Interpret("ROLLBACK");
// }
// TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) {
// Interpret("BEGIN");
// ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException);
// Interpret("ROLLBACK");
// ASSERT_THROW(Interpret("DROP SCHEMA ON :label"),
// memgraph::query::v2::ConstraintInMulticommandTxException); Interpret("ROLLBACK");
// }
// TEST_F(InterpreterTest, SchemaTestCreateAndShow) {
// // Empty schema type map should result with syntax exception.
// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::v2::SyntaxException);
// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"),
// memgraph::frontend::opencypher::SyntaxException);
// // Duplicate properties are should also cause an exception
// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"),
// memgraph::query::v2::SemanticException); ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name
// INTEGER);"),
// memgraph::query::v2::SemanticException); ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name
// STRING, name INTEGER);"),
// memgraph::query::v2::SemanticException);
// {
// // Cannot create same schema twice
// Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::v2::QueryException);
// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"),
// memgraph::query::v2::QueryException);
// }
// // Show schema
// {
@ -1610,7 +1621,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// ASSERT_TRUE(result_table.contains(key1));
// const auto primary_key_split = StringToUnorderedSet(result[1].ValueString());
// ASSERT_EQ(primary_key_split.size(), 2);
// ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " << result[1].ValueString();
// ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " <<
// result[1].ValueString();
// const auto &result2 = stream.GetResults().front();
// ASSERT_EQ(result2.size(), 2U);
@ -1618,14 +1630,16 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) {
// ASSERT_TRUE(result_table.contains(key2));
// const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString());
// ASSERT_EQ(primary_key_split2.size(), 2);
// ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " << result[1].ValueString();
// ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " <<
// result[1].ValueString();
// }
// }
// TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) {
// Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
// // Wrong syntax for dropping schema.
// ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::v2::SyntaxException);
// ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"),
// memgraph::frontend::opencypher::SyntaxException);
// // Cannot drop non existant schema.
// ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::v2::QueryException);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More