memgraph/src/query/trigger_context.hpp
2022-03-14 15:47:41 +01:00

365 lines
14 KiB
C++

// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <map>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "query/db_accessor.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/view.hpp"
#include "utils/concepts.hpp"
#include "utils/fnv.hpp"
namespace memgraph::query {
namespace detail {
template <typename T>
concept ObjectAccessor = utils::SameAsAnyOf<T, VertexAccessor, EdgeAccessor>;
template <ObjectAccessor TAccessor>
const char *ObjectString() {
if constexpr (std::same_as<TAccessor, VertexAccessor>) {
return "vertex";
} else {
return "edge";
}
}
template <ObjectAccessor TAccessor>
struct CreatedObject {
explicit CreatedObject(const TAccessor &object) : object{object} {}
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> ToMap([[maybe_unused]] DbAccessor *dba) const {
return {{ObjectString<TAccessor>(), TypedValue{object}}};
}
TAccessor object;
};
template <ObjectAccessor TAccessor>
struct DeletedObject {
explicit DeletedObject(const TAccessor &object) : object{object} {}
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> ToMap([[maybe_unused]] DbAccessor *dba) const {
return {{ObjectString<TAccessor>(), TypedValue{object}}};
}
TAccessor object;
};
template <ObjectAccessor TAccessor>
struct SetObjectProperty {
explicit SetObjectProperty(const TAccessor &object, storage::PropertyId key, TypedValue old_value,
TypedValue new_value)
: object{object}, key{key}, old_value{std::move(old_value)}, new_value{std::move(new_value)} {}
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
return {{ObjectString<TAccessor>(), TypedValue{object}},
{"key", TypedValue{dba->PropertyToName(key)}},
{"old", old_value},
{"new", new_value}};
}
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
TAccessor object;
storage::PropertyId key;
TypedValue old_value;
TypedValue new_value;
};
template <ObjectAccessor TAccessor>
struct RemovedObjectProperty {
explicit RemovedObjectProperty(const TAccessor &object, storage::PropertyId key, TypedValue old_value)
: object{object}, key{key}, old_value{std::move(old_value)} {}
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
return {{ObjectString<TAccessor>(), TypedValue{object}},
{"key", TypedValue{dba->PropertyToName(key)}},
{"old", old_value}};
}
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
TAccessor object;
storage::PropertyId key;
TypedValue old_value;
};
struct SetVertexLabel {
explicit SetVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id)
: object{vertex}, label_id{label_id} {}
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const;
bool IsValid() const;
VertexAccessor object;
storage::LabelId label_id;
};
struct RemovedVertexLabel {
explicit RemovedVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id)
: object{vertex}, label_id{label_id} {}
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const;
bool IsValid() const;
VertexAccessor object;
storage::LabelId label_id;
};
} // namespace detail
enum class TriggerIdentifierTag : uint8_t {
CREATED_VERTICES,
CREATED_EDGES,
CREATED_OBJECTS,
DELETED_VERTICES,
DELETED_EDGES,
DELETED_OBJECTS,
SET_VERTEX_PROPERTIES,
SET_EDGE_PROPERTIES,
REMOVED_VERTEX_PROPERTIES,
REMOVED_EDGE_PROPERTIES,
SET_VERTEX_LABELS,
REMOVED_VERTEX_LABELS,
UPDATED_VERTICES,
UPDATED_EDGES,
UPDATED_OBJECTS
};
enum class TriggerEventType : uint8_t {
ANY, // Triggers on any change
VERTEX_CREATE,
EDGE_CREATE,
CREATE,
VERTEX_DELETE,
EDGE_DELETE,
DELETE,
VERTEX_UPDATE,
EDGE_UPDATE,
UPDATE
};
const char *TriggerEventTypeToString(TriggerEventType event_type);
static_assert(std::is_trivially_copy_constructible_v<VertexAccessor>,
"VertexAccessor is not trivially copy constructible, move it where possible and remove this assert");
static_assert(std::is_trivially_copy_constructible_v<EdgeAccessor>,
"EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert");
// Holds the information necessary for triggers
class TriggerContext {
public:
TriggerContext() = default;
TriggerContext(std::vector<detail::CreatedObject<VertexAccessor>> created_vertices,
std::vector<detail::DeletedObject<VertexAccessor>> deleted_vertices,
std::vector<detail::SetObjectProperty<VertexAccessor>> set_vertex_properties,
std::vector<detail::RemovedObjectProperty<VertexAccessor>> removed_vertex_properties,
std::vector<detail::SetVertexLabel> set_vertex_labels,
std::vector<detail::RemovedVertexLabel> removed_vertex_labels,
std::vector<detail::CreatedObject<EdgeAccessor>> created_edges,
std::vector<detail::DeletedObject<EdgeAccessor>> deleted_edges,
std::vector<detail::SetObjectProperty<EdgeAccessor>> set_edge_properties,
std::vector<detail::RemovedObjectProperty<EdgeAccessor>> removed_edge_properties)
: created_vertices_{std::move(created_vertices)},
deleted_vertices_{std::move(deleted_vertices)},
set_vertex_properties_{std::move(set_vertex_properties)},
removed_vertex_properties_{std::move(removed_vertex_properties)},
set_vertex_labels_{std::move(set_vertex_labels)},
removed_vertex_labels_{std::move(removed_vertex_labels)},
created_edges_{std::move(created_edges)},
deleted_edges_{std::move(deleted_edges)},
set_edge_properties_{std::move(set_edge_properties)},
removed_edge_properties_{std::move(removed_edge_properties)} {}
TriggerContext(const TriggerContext &) = default;
TriggerContext(TriggerContext &&) = default;
TriggerContext &operator=(const TriggerContext &) = default;
TriggerContext &operator=(TriggerContext &&) = default;
// Adapt the TriggerContext object inplace for a different DbAccessor
// (each derived accessor, e.g. VertexAccessor, gets adapted
// to the sent DbAccessor so they can be used safely)
void AdaptForAccessor(DbAccessor *accessor);
// Get TypedValue for the identifier defined with tag
TypedValue GetTypedValue(TriggerIdentifierTag tag, DbAccessor *dba) const;
bool ShouldEventTrigger(TriggerEventType) const;
private:
std::vector<detail::CreatedObject<VertexAccessor>> created_vertices_;
std::vector<detail::DeletedObject<VertexAccessor>> deleted_vertices_;
std::vector<detail::SetObjectProperty<VertexAccessor>> set_vertex_properties_;
std::vector<detail::RemovedObjectProperty<VertexAccessor>> removed_vertex_properties_;
std::vector<detail::SetVertexLabel> set_vertex_labels_;
std::vector<detail::RemovedVertexLabel> removed_vertex_labels_;
std::vector<detail::CreatedObject<EdgeAccessor>> created_edges_;
std::vector<detail::DeletedObject<EdgeAccessor>> deleted_edges_;
std::vector<detail::SetObjectProperty<EdgeAccessor>> set_edge_properties_;
std::vector<detail::RemovedObjectProperty<EdgeAccessor>> removed_edge_properties_;
};
// Collects the information necessary for triggers during a single transaction run.
class TriggerContextCollector {
public:
struct HashPairWithAccessor {
template <detail::ObjectAccessor TAccessor, typename T2>
size_t operator()(const std::pair<TAccessor, T2> &pair) const {
using GidType = decltype(std::declval<TAccessor>().Gid());
return utils::HashCombine<GidType, T2>{}(pair.first.Gid(), pair.second);
}
};
struct PropertyChangeInfo {
TypedValue old_value;
TypedValue new_value;
};
template <detail::ObjectAccessor TAccessor>
using PropertyChangesMap =
std::unordered_map<std::pair<TAccessor, storage::PropertyId>, PropertyChangeInfo, HashPairWithAccessor>;
template <detail::ObjectAccessor TAccessor>
struct Registry {
bool should_register_created_objects{false};
bool should_register_deleted_objects{false};
bool should_register_updated_objects{false}; // Set/removed properties (and labels for vertices)
std::unordered_map<storage::Gid, detail::CreatedObject<TAccessor>> created_objects;
std::vector<detail::DeletedObject<TAccessor>> deleted_objects;
// During the transaction, a single property on a single object could be changed multiple times.
// We want to register only the global change, at the end of the transaction. The change consists of
// the value before the transaction start, and the latest value assigned throughout the transaction.
PropertyChangesMap<TAccessor> property_changes;
};
explicit TriggerContextCollector(const std::unordered_set<TriggerEventType> &event_types);
TriggerContextCollector(const TriggerContextCollector &) = default;
TriggerContextCollector(TriggerContextCollector &&) = default;
TriggerContextCollector &operator=(const TriggerContextCollector &) = default;
TriggerContextCollector &operator=(TriggerContextCollector &&) = default;
~TriggerContextCollector() = default;
template <detail::ObjectAccessor TAccessor>
bool ShouldRegisterCreatedObject() const {
return GetRegistry<TAccessor>().should_register_created_objects;
}
template <detail::ObjectAccessor TAccessor>
void RegisterCreatedObject(const TAccessor &created_object) {
auto &registry = GetRegistry<TAccessor>();
if (!registry.should_register_created_objects) {
return;
}
registry.created_objects.emplace(created_object.Gid(), detail::CreatedObject{created_object});
}
template <detail::ObjectAccessor TAccessor>
bool ShouldRegisterDeletedObject() const {
return GetRegistry<TAccessor>().should_register_deleted_objects;
}
template <detail::ObjectAccessor TAccessor>
void RegisterDeletedObject(const TAccessor &deleted_object) {
auto &registry = GetRegistry<TAccessor>();
if (!registry.should_register_deleted_objects || registry.created_objects.count(deleted_object.Gid())) {
return;
}
registry.deleted_objects.emplace_back(deleted_object);
}
template <detail::ObjectAccessor TAccessor>
bool ShouldRegisterObjectPropertyChange() const {
return GetRegistry<TAccessor>().should_register_updated_objects;
}
template <detail::ObjectAccessor TAccessor>
void RegisterSetObjectProperty(const TAccessor &object, const storage::PropertyId key, TypedValue old_value,
TypedValue new_value) {
auto &registry = GetRegistry<TAccessor>();
if (!registry.should_register_updated_objects) {
return;
}
if (registry.created_objects.count(object.Gid())) {
return;
}
if (auto it = registry.property_changes.find({object, key}); it != registry.property_changes.end()) {
it->second.new_value = std::move(new_value);
return;
}
registry.property_changes.emplace(std::make_pair(object, key),
PropertyChangeInfo{std::move(old_value), std::move(new_value)});
}
template <detail::ObjectAccessor TAccessor>
void RegisterRemovedObjectProperty(const TAccessor &object, const storage::PropertyId key, TypedValue old_value) {
// property is already removed
if (old_value.IsNull()) {
return;
}
RegisterSetObjectProperty(object, key, std::move(old_value), TypedValue());
}
bool ShouldRegisterVertexLabelChange() const;
void RegisterSetVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
void RegisterRemovedVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
[[nodiscard]] TriggerContext TransformToTriggerContext() &&;
private:
template <detail::ObjectAccessor TAccessor>
const Registry<TAccessor> &GetRegistry() const {
if constexpr (std::same_as<TAccessor, VertexAccessor>) {
return vertex_registry_;
} else {
return edge_registry_;
}
}
template <detail::ObjectAccessor TAccessor>
Registry<TAccessor> &GetRegistry() {
return const_cast<Registry<TAccessor> &>(
const_cast<const TriggerContextCollector *>(this)->GetRegistry<TAccessor>());
}
using LabelChangesMap = std::unordered_map<std::pair<VertexAccessor, storage::LabelId>, int8_t, HashPairWithAccessor>;
using LabelChangesLists = std::pair<std::vector<detail::SetVertexLabel>, std::vector<detail::RemovedVertexLabel>>;
enum class LabelChange : int8_t { REMOVE = -1, ADD = 1 };
static int8_t LabelChangeToInt(LabelChange change);
[[nodiscard]] static LabelChangesLists LabelMapToList(LabelChangesMap &&label_changes);
void UpdateLabelMap(VertexAccessor vertex, storage::LabelId label_id, LabelChange change);
Registry<VertexAccessor> vertex_registry_;
Registry<EdgeAccessor> edge_registry_;
// During the transaction, a single label on a single vertex could be added and removed multiple times.
// We want to register only the global change, at the end of the transaction. The change consists of
// the state of the label before the transaction start, and the latest state assigned throughout the transaction.
LabelChangesMap label_changes_;
};
} // namespace memgraph::query