Add filtering based on registered event types (#155)

* Add filtering to TriggerContextCollector

* Add all predefined variable to ANY triggers

* Make variable names consistent with event types
This commit is contained in:
János Benjamin Antal 2021-05-25 14:18:27 +02:00 committed by Antonio Andelic
parent 1abee1ed3a
commit e8a1d15a55
10 changed files with 1245 additions and 863 deletions

View File

@ -4,6 +4,7 @@
### Major Feature and Improvements
* Added triggers.
* Replaced mg_client with mgconsole
### Bug Fixes

View File

@ -33,6 +33,7 @@ set(mg_query_sources
procedure/py_module.cpp
serialization/property_value.cpp
trigger.cpp
trigger_context.cpp
typed_value.cpp)
add_library(mg-query STATIC ${mg_query_sources})

View File

@ -606,58 +606,6 @@ InterpreterContext::InterpreterContext(storage::Storage *db, const std::filesyst
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
// try {
// {
// auto storage_acc = interpreter_context_->db->Access();
// DbAccessor dba(&storage_acc);
// auto triggers_acc = interpreter_context_->before_commit_triggers.access();
// triggers_acc.insert(Trigger{"BeforeDelete",
// "UNWIND deletedVertices as u CREATE(:DELETED_VERTEX {id: id(u) + 10})",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_DELETE});
// triggers_acc.insert(Trigger{"BeforeUpdatePropertyi",
// "UNWIND assignedVertexProperties as u SET u.vertex.two = u.new",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_UPDATE});
// triggers_acc.insert(Trigger{"BeforeDeleteEdge", "UNWIND deletedEdges as u CREATE(:DELETED_EDGE {id: id(u) +10})
// ",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::EDGE_DELETE});
// // triggers_acc.insert(Trigger{"BeforeDelete2", "UNWIND deletedEdges as u SET u.deleted = 0",
// // &interpreter_context_->ast_cache, &dba,
// // &interpreter_context_->antlr_lock});
// triggers_acc.insert(Trigger{"BeforeDeleteProcedure",
// "CALL script.procedure('VERTEX_UPDATE', updatedVertices) YIELD * RETURN *",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_UPDATE});
// triggers_acc.insert(Trigger{"BeforeCreator", "UNWIND createdVertices as u SET u.before = id(u) + 10",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_CREATE});
// triggers_acc.insert(Trigger{"BeforeCreatorEdge", "UNWIND createdEdges as u SET u.before = id(u) + 10",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::EDGE_CREATE});
// triggers_acc.insert(Trigger{"BeforeSetLabelProcedure",
// "CALL label.procedure('VERTEX_UPDATE', assignedVertexLabels) YIELD * RETURN *",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_UPDATE});
// }
// {
// auto storage_acc = interpreter_context->db->Access();
// DbAccessor dba(&storage_acc);
// auto triggers_acc = interpreter_context->after_commit_triggers.access();
// triggers_acc.insert(Trigger{"AfterDelete", "UNWIND deletedVertices as u CREATE(:DELETED {id: u.id + 100})",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_DELETE});
// triggers_acc.insert(Trigger{"AfterCreator", "UNWIND createdVertices as u SET u.after = u.id + 100",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock,
// TriggerEventType::VERTEX_CREATE});
// triggers_acc.insert(Trigger{
// "AfterUpdateProcedure", "CALL script.procedure('UPDATE',updatedObjects) YIELD * RETURN *",
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock, TriggerEventType::UPDATE});
// }
// } catch (const utils::BasicException &e) {
// spdlog::critical("Failed to create a trigger because: {}", e.what());
// }
}
PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) {
@ -675,7 +623,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
execution_db_accessor_.emplace(db_accessor_.get());
if (interpreter_context_->trigger_store->HasTriggers()) {
trigger_context_collector_.emplace();
trigger_context_collector_.emplace(interpreter_context_->trigger_store->GetEventTypes());
}
};
} else if (query_upper == "COMMIT") {
@ -1504,7 +1452,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
execution_db_accessor_.emplace(db_accessor_.get());
if (utils::Downcast<CypherQuery>(parsed_query.query) && interpreter_context_->trigger_store->HasTriggers()) {
trigger_context_collector_.emplace();
trigger_context_collector_.emplace(interpreter_context_->trigger_store->GetEventTypes());
}
}
@ -1648,6 +1596,7 @@ void Interpreter::Commit() {
std::optional<TriggerContext> trigger_context = std::nullopt;
if (trigger_context_collector_) {
trigger_context.emplace(std::move(*trigger_context_collector_).TransformToTriggerContext());
trigger_context_collector_.reset();
}
if (trigger_context) {
@ -1667,6 +1616,12 @@ void Interpreter::Commit() {
SPDLOG_DEBUG("Finished executing before commit triggers");
}
const auto reset_necessary_members = [this]() {
execution_db_accessor_.reset();
db_accessor_.reset();
trigger_context_collector_.reset();
};
auto maybe_constraint_violation = db_accessor_->Commit();
if (maybe_constraint_violation.HasError()) {
const auto &constraint_violation = maybe_constraint_violation.GetError();
@ -1675,9 +1630,7 @@ void Interpreter::Commit() {
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());
execution_db_accessor_.reset();
db_accessor_.reset();
trigger_context_collector_.reset();
reset_necessary_members();
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
property_name);
break;
@ -1688,9 +1641,7 @@ void Interpreter::Commit() {
utils::PrintIterable(
property_names_stream, constraint_violation.properties, ", ",
[this](auto &stream, const auto &prop) { stream << execution_db_accessor_->PropertyToName(prop); });
execution_db_accessor_.reset();
db_accessor_.reset();
trigger_context_collector_.reset();
reset_necessary_members();
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
property_names_stream.str());
break;
@ -1714,9 +1665,7 @@ void Interpreter::Commit() {
});
}
execution_db_accessor_.reset();
db_accessor_.reset();
trigger_context_collector_.reset();
reset_necessary_members();
SPDLOG_DEBUG("Finished comitting the transaction");
}

View File

@ -1873,7 +1873,8 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
throw QueryRuntimeException("Unexpected error when deleting a node.");
}
}
if (context.trigger_context_collector && res.GetValue()) {
if (context.trigger_context_collector &&
context.trigger_context_collector->ShouldRegisterDeletedObject<EdgeAccessor>() && res.GetValue()) {
context.trigger_context_collector->RegisterDeletedObject(res.GetValue()->first);
for (const auto &deleted_edge : res.GetValue()->second) {
context.trigger_context_collector->RegisterDeletedObject(deleted_edge);
@ -2025,6 +2026,9 @@ template <AccessorWithProperties TRecordAccessor>
void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op,
ExecutionContext *context) {
std::optional<std::map<storage::PropertyId, storage::PropertyValue>> old_values;
const bool should_register_change =
context->trigger_context_collector &&
context->trigger_context_collector->ShouldRegisterObjectPropertyChange<TRecordAccessor>();
if (op == SetProperties::Op::REPLACE) {
auto maybe_value = record->ClearProperties();
if (maybe_value.HasError()) {
@ -2041,7 +2045,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
}
}
if (context->trigger_context_collector) {
if (should_register_change) {
old_values.emplace(std::move(*maybe_value));
}
}
@ -2063,10 +2067,10 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
return *maybe_props;
};
auto register_set_property = [&](auto returned_old_value, auto key, auto new_value) {
auto register_set_property = [&](auto &&returned_old_value, auto key, auto &&new_value) {
auto old_value = [&]() -> storage::PropertyValue {
if (!old_values) {
return std::move(returned_old_value);
return std::forward<decltype(returned_old_value)>(returned_old_value);
}
if (auto it = old_values->find(key); it != old_values->end()) {
@ -2075,8 +2079,9 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
return {};
}();
context->trigger_context_collector->RegisterSetObjectProperty(*record, key, TypedValue(std::move(old_value)),
TypedValue(std::move(new_value)));
context->trigger_context_collector->RegisterSetObjectProperty(
*record, key, TypedValue(std::move(old_value)), TypedValue(std::forward<decltype(new_value)>(new_value)));
};
auto set_props = [&, record](auto properties) {
@ -2096,7 +2101,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
}
}
if (context->trigger_context_collector) {
if (should_register_change) {
register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second));
}
}
@ -2113,7 +2118,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
for (const auto &kv : rhs.ValueMap()) {
auto key = context->db_accessor->NameToProperty(kv.first);
auto old_value = PropsSetChecked(record, key, kv.second);
if (context->trigger_context_collector) {
if (should_register_change) {
register_set_property(std::move(old_value), key, kv.second);
}
}
@ -2125,7 +2130,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
"map.");
}
if (context->trigger_context_collector && old_values) {
if (should_register_change && old_values) {
// register removed properties
for (auto &[property_id, property_value] : *old_values) {
context->trigger_context_collector->RegisterRemovedObjectProperty(*record, property_id,

View File

@ -14,7 +14,6 @@
namespace query {
namespace {
auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
switch (tag) {
case TriggerIdentifierTag::CREATED_VERTICES:
@ -36,10 +35,10 @@ auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
return "deletedObjects";
case TriggerIdentifierTag::SET_VERTEX_PROPERTIES:
return "assignedVertexProperties";
return "setVertexProperties";
case TriggerIdentifierTag::SET_EDGE_PROPERTIES:
return "assignedEdgeProperties";
return "setEdgeProperties";
case TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES:
return "removedVertexProperties";
@ -48,7 +47,7 @@ auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
return "removedEdgeProperties";
case TriggerIdentifierTag::SET_VERTEX_LABELS:
return "assignedVertexLabels";
return "setVertexLabels";
case TriggerIdentifierTag::REMOVED_VERTEX_LABELS:
return "removedVertexLabels";
@ -87,7 +86,13 @@ std::vector<std::pair<Identifier, TriggerIdentifierTag>> GetPredefinedIdentifier
switch (event_type) {
case EventType::ANY:
return {};
return TagsToIdentifiers(
IdentifierTag::CREATED_VERTICES, IdentifierTag::CREATED_EDGES, IdentifierTag::CREATED_OBJECTS,
IdentifierTag::DELETED_VERTICES, IdentifierTag::DELETED_EDGES, IdentifierTag::DELETED_OBJECTS,
IdentifierTag::SET_VERTEX_PROPERTIES, IdentifierTag::SET_EDGE_PROPERTIES,
IdentifierTag::REMOVED_VERTEX_PROPERTIES, IdentifierTag::REMOVED_EDGE_PROPERTIES,
IdentifierTag::SET_VERTEX_LABELS, IdentifierTag::REMOVED_VERTEX_LABELS, IdentifierTag::UPDATED_VERTICES,
IdentifierTag::UPDATED_EDGES, IdentifierTag::UPDATED_OBJECTS);
case EventType::CREATE:
return TagsToIdentifiers(IdentifierTag::CREATED_OBJECTS);
@ -120,420 +125,7 @@ std::vector<std::pair<Identifier, TriggerIdentifierTag>> GetPredefinedIdentifier
IdentifierTag::UPDATED_EDGES);
}
}
template <typename T>
concept WithToMap = requires(const T value, DbAccessor *dba) {
{ value.ToMap(dba) }
->std::same_as<std::map<std::string, TypedValue>>;
};
template <WithToMap T>
TypedValue ToTypedValue(const T &value, DbAccessor *dba) {
return TypedValue{value.ToMap(dba)};
}
template <detail::ObjectAccessor TAccessor>
TypedValue ToTypedValue(const detail::CreatedObject<TAccessor> &created_object, [[maybe_unused]] DbAccessor *dba) {
return TypedValue{created_object.object};
}
template <detail::ObjectAccessor TAccessor>
TypedValue ToTypedValue(const detail::DeletedObject<TAccessor> &deleted_object, [[maybe_unused]] DbAccessor *dba) {
return TypedValue{deleted_object.object};
}
template <typename T>
concept WithIsValid = requires(const T value) {
{ value.IsValid() }
->std::same_as<bool>;
};
template <typename T>
concept ConvertableToTypedValue = requires(T value, DbAccessor *dba) {
{ ToTypedValue(value, dba) }
->std::same_as<TypedValue>;
}
&&WithIsValid<T>;
template <typename T>
concept LabelUpdateContext = utils::SameAsAnyOf<T, detail::SetVertexLabel, detail::RemovedVertexLabel>;
template <LabelUpdateContext TContext>
TypedValue ToTypedValue(const std::vector<TContext> &values, DbAccessor *dba) {
std::unordered_map<storage::LabelId, std::vector<TypedValue>> vertices_by_labels;
for (const auto &value : values) {
if (value.IsValid()) {
vertices_by_labels[value.label_id].emplace_back(value.object);
}
}
TypedValue result{std::vector<TypedValue>{}};
auto &typed_values = result.ValueList();
for (auto &[label_id, vertices] : vertices_by_labels) {
typed_values.emplace_back(std::map<std::string, TypedValue>{
{std::string{"label"}, TypedValue(dba->LabelToName(label_id))},
{std::string{"vertices"}, TypedValue(std::move(vertices))},
});
}
return result;
}
template <ConvertableToTypedValue T>
TypedValue ToTypedValue(const std::vector<T> &values, DbAccessor *dba) requires(!LabelUpdateContext<T>) {
TypedValue result{std::vector<TypedValue>{}};
auto &typed_values = result.ValueList();
typed_values.reserve(values.size());
for (const auto &value : values) {
if (value.IsValid()) {
typed_values.push_back(ToTypedValue(value, dba));
}
}
return result;
}
template <typename T>
const char *TypeToString() {
if constexpr (std::same_as<T, detail::CreatedObject<VertexAccessor>>) {
return "created_vertex";
} else if constexpr (std::same_as<T, detail::CreatedObject<EdgeAccessor>>) {
return "created_edge";
} else if constexpr (std::same_as<T, detail::DeletedObject<VertexAccessor>>) {
return "deleted_vertex";
} else if constexpr (std::same_as<T, detail::DeletedObject<EdgeAccessor>>) {
return "deleted_edge";
} else if constexpr (std::same_as<T, detail::SetObjectProperty<VertexAccessor>>) {
return "set_vertex_property";
} else if constexpr (std::same_as<T, detail::SetObjectProperty<EdgeAccessor>>) {
return "set_edge_property";
} else if constexpr (std::same_as<T, detail::RemovedObjectProperty<VertexAccessor>>) {
return "removed_vertex_property";
} else if constexpr (std::same_as<T, detail::RemovedObjectProperty<EdgeAccessor>>) {
return "removed_edge_property";
} else if constexpr (std::same_as<T, detail::SetVertexLabel>) {
return "set_vertex_label";
} else if constexpr (std::same_as<T, detail::RemovedVertexLabel>) {
return "removed_vertex_label";
}
}
template <typename T>
concept ContextInfo = WithToMap<T> &&WithIsValid<T>;
template <ContextInfo... Args>
TypedValue Concatenate(DbAccessor *dba, const std::vector<Args> &...args) {
const auto size = (args.size() + ...);
TypedValue result{std::vector<TypedValue>{}};
auto &concatenated = result.ValueList();
concatenated.reserve(size);
const auto add_to_concatenated = [&]<ContextInfo T>(const std::vector<T> &values) {
for (const auto &value : values) {
if (value.IsValid()) {
auto map = value.ToMap(dba);
map["event_type"] = TypeToString<T>();
concatenated.emplace_back(std::move(map));
}
}
};
(add_to_concatenated(args), ...);
return result;
}
template <typename T>
concept WithEmpty = requires(const T value) {
{ value.empty() }
->std::same_as<bool>;
};
template <WithEmpty... TContainer>
bool AnyContainsValue(const TContainer &...value_containers) {
return (!value_containers.empty() || ...);
}
} // namespace
namespace detail {
bool SetVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> SetVertexLabel::ToMap(DbAccessor *dba) const {
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
}
bool RemovedVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> RemovedVertexLabel::ToMap(DbAccessor *dba) const {
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
}
} // namespace detail
const char *TriggerEventTypeToString(const TriggerEventType event_type) {
switch (event_type) {
case TriggerEventType::ANY:
return "ANY";
case TriggerEventType::CREATE:
return "CREATE";
case TriggerEventType::VERTEX_CREATE:
return "() CREATE";
case TriggerEventType::EDGE_CREATE:
return "--> CREATE";
case TriggerEventType::DELETE:
return "DELETE";
case TriggerEventType::VERTEX_DELETE:
return "() DELETE";
case TriggerEventType::EDGE_DELETE:
return "--> DELETE";
case TriggerEventType::UPDATE:
return "UPDATE";
case TriggerEventType::VERTEX_UPDATE:
return "() UPDATE";
case TriggerEventType::EDGE_UPDATE:
return "--> UPDATE";
}
}
void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
{
// adapt created_vertices_
auto it = created_vertices_.begin();
for (auto &created_vertex : created_vertices_) {
if (auto maybe_vertex = accessor->FindVertex(created_vertex.object.Gid(), storage::View::OLD); maybe_vertex) {
*it = detail::CreatedObject{*maybe_vertex};
++it;
}
}
created_vertices_.erase(it, created_vertices_.end());
}
// deleted_vertices_ should keep the transaction context of the transaction which deleted it
// because no other transaction can modify an object after it's deleted so it should be the
// latest state of the object
const auto adapt_context_with_vertex = [accessor](auto *values) {
auto it = values->begin();
for (auto &value : *values) {
if (auto maybe_vertex = accessor->FindVertex(value.object.Gid(), storage::View::OLD); maybe_vertex) {
*it = std::move(value);
it->object = *maybe_vertex;
++it;
}
}
values->erase(it, values->end());
};
adapt_context_with_vertex(&set_vertex_properties_);
adapt_context_with_vertex(&removed_vertex_properties_);
adapt_context_with_vertex(&set_vertex_labels_);
adapt_context_with_vertex(&removed_vertex_labels_);
{
// adapt created_edges
auto it = created_edges_.begin();
for (auto &created_edge : created_edges_) {
const auto maybe_from_vertex = accessor->FindVertex(created_edge.object.From().Gid(), storage::View::OLD);
if (!maybe_from_vertex) {
continue;
}
auto maybe_out_edges = maybe_from_vertex->OutEdges(storage::View::OLD);
MG_ASSERT(maybe_out_edges.HasValue());
const auto edge_gid = created_edge.object.Gid();
for (const auto &edge : *maybe_out_edges) {
if (edge.Gid() == edge_gid) {
*it = detail::CreatedObject{edge};
++it;
}
}
}
created_edges_.erase(it, created_edges_.end());
}
// deleted_edges_ should keep the transaction context of the transaction which deleted it
// because no other transaction can modify an object after it's deleted so it should be the
// latest state of the object
const auto adapt_context_with_edge = [accessor](auto *values) {
auto it = values->begin();
for (const auto &value : *values) {
if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::View::OLD); maybe_vertex) {
auto maybe_out_edges = maybe_vertex->OutEdges(storage::View::OLD);
MG_ASSERT(maybe_out_edges.HasValue());
for (const auto &edge : *maybe_out_edges) {
if (edge.Gid() == value.object.Gid()) {
*it = std::move(value);
it->object = edge;
++it;
break;
}
}
}
}
values->erase(it, values->end());
};
adapt_context_with_edge(&set_edge_properties_);
adapt_context_with_edge(&removed_edge_properties_);
}
TypedValue TriggerContext::GetTypedValue(const TriggerIdentifierTag tag, DbAccessor *dba) const {
switch (tag) {
case TriggerIdentifierTag::CREATED_VERTICES:
return ToTypedValue(created_vertices_, dba);
case TriggerIdentifierTag::CREATED_EDGES:
return ToTypedValue(created_edges_, dba);
case TriggerIdentifierTag::CREATED_OBJECTS:
return Concatenate(dba, created_vertices_, created_edges_);
case TriggerIdentifierTag::DELETED_VERTICES:
return ToTypedValue(deleted_vertices_, dba);
case TriggerIdentifierTag::DELETED_EDGES:
return ToTypedValue(deleted_edges_, dba);
case TriggerIdentifierTag::DELETED_OBJECTS:
return Concatenate(dba, deleted_vertices_, deleted_edges_);
case TriggerIdentifierTag::SET_VERTEX_PROPERTIES:
return ToTypedValue(set_vertex_properties_, dba);
case TriggerIdentifierTag::SET_EDGE_PROPERTIES:
return ToTypedValue(set_edge_properties_, dba);
case TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES:
return ToTypedValue(removed_vertex_properties_, dba);
case TriggerIdentifierTag::REMOVED_EDGE_PROPERTIES:
return ToTypedValue(removed_edge_properties_, dba);
case TriggerIdentifierTag::SET_VERTEX_LABELS:
return ToTypedValue(set_vertex_labels_, dba);
case TriggerIdentifierTag::REMOVED_VERTEX_LABELS:
return ToTypedValue(removed_vertex_labels_, dba);
case TriggerIdentifierTag::UPDATED_VERTICES:
return Concatenate(dba, set_vertex_properties_, removed_vertex_properties_, set_vertex_labels_,
removed_vertex_labels_);
case TriggerIdentifierTag::UPDATED_EDGES:
return Concatenate(dba, set_edge_properties_, removed_edge_properties_);
case TriggerIdentifierTag::UPDATED_OBJECTS:
return Concatenate(dba, set_vertex_properties_, set_edge_properties_, removed_vertex_properties_,
removed_edge_properties_, set_vertex_labels_, removed_vertex_labels_);
}
}
bool TriggerContext::ShouldEventTrigger(const TriggerEventType event_type) const {
using EventType = TriggerEventType;
switch (event_type) {
case EventType::ANY:
return true;
case EventType::CREATE:
return AnyContainsValue(created_vertices_, created_edges_);
case EventType::VERTEX_CREATE:
return AnyContainsValue(created_vertices_);
case EventType::EDGE_CREATE:
return AnyContainsValue(created_edges_);
case EventType::DELETE:
return AnyContainsValue(deleted_vertices_, deleted_edges_);
case EventType::VERTEX_DELETE:
return AnyContainsValue(deleted_vertices_);
case EventType::EDGE_DELETE:
return AnyContainsValue(deleted_edges_);
case EventType::UPDATE:
return AnyContainsValue(set_vertex_properties_, set_edge_properties_, removed_vertex_properties_,
removed_edge_properties_, set_vertex_labels_, removed_vertex_labels_);
case EventType::VERTEX_UPDATE:
return AnyContainsValue(set_vertex_properties_, removed_vertex_properties_, set_vertex_labels_,
removed_vertex_labels_);
case EventType::EDGE_UPDATE:
return AnyContainsValue(set_edge_properties_, removed_edge_properties_);
}
}
void TriggerContextCollector::UpdateLabelMap(const VertexAccessor vertex, const storage::LabelId label_id,
const LabelChange change) {
auto &registry = GetRegistry<VertexAccessor>();
if (registry.created_objects_.count(vertex.Gid())) {
return;
}
if (auto it = label_changes_.find({vertex, label_id}); it != label_changes_.end()) {
it->second = std::clamp(it->second + LabelChangeToInt(change), -1, 1);
return;
}
label_changes_.emplace(std::make_pair(vertex, label_id), LabelChangeToInt(change));
}
void TriggerContextCollector::RegisterSetVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id) {
UpdateLabelMap(vertex, label_id, LabelChange::ADD);
}
void TriggerContextCollector::RegisterRemovedVertexLabel(const VertexAccessor &vertex,
const storage::LabelId label_id) {
UpdateLabelMap(vertex, label_id, LabelChange::REMOVE);
}
int8_t TriggerContextCollector::LabelChangeToInt(LabelChange change) {
static_assert(std::is_same_v<std::underlying_type_t<LabelChange>, int8_t>,
"The underlying type of LabelChange doesn't match the return type!");
return static_cast<int8_t>(change);
}
TriggerContext TriggerContextCollector::TransformToTriggerContext() && {
auto [created_vertices, deleted_vertices, set_vertex_properties, removed_vertex_properties] =
std::move(vertex_registry_).Summarize();
auto [set_vertex_labels, removed_vertex_labels] = LabelMapToList(std::move(label_changes_));
auto [created_edges, deleted_edges, set_edge_properties, removed_edge_properties] =
std::move(edge_registry_).Summarize();
return {std::move(created_vertices), std::move(deleted_vertices),
std::move(set_vertex_properties), std::move(removed_vertex_properties),
std::move(set_vertex_labels), std::move(removed_vertex_labels),
std::move(created_edges), std::move(deleted_edges),
std::move(set_edge_properties), std::move(removed_edge_properties)};
}
TriggerContextCollector::LabelChangesLists TriggerContextCollector::LabelMapToList(LabelChangesMap &&label_changes) {
std::vector<detail::SetVertexLabel> set_vertex_labels;
std::vector<detail::RemovedVertexLabel> removed_vertex_labels;
for (const auto &[key, label_state] : label_changes) {
if (label_state == LabelChangeToInt(LabelChange::ADD)) {
set_vertex_labels.emplace_back(key.first, key.second);
} else if (label_state == LabelChangeToInt(LabelChange::REMOVE)) {
removed_vertex_labels.emplace_back(key.first, key.second);
}
}
label_changes.clear();
return {std::move(set_vertex_labels), std::move(removed_vertex_labels)};
}
Trigger::Trigger(std::string name, const std::string &query,
const std::map<std::string, storage::PropertyValue> &user_parameters,
@ -777,4 +369,18 @@ std::vector<TriggerStore::TriggerInfo> TriggerStore::GetTriggerInfo() const {
return info;
}
std::unordered_set<TriggerEventType> TriggerStore::GetEventTypes() const {
std::unordered_set<TriggerEventType> event_types;
const auto add_event_types = [&](const utils::SkipList<Trigger> &trigger_list) {
for (const auto &trigger : trigger_list.access()) {
event_types.insert(trigger.EventType());
}
};
add_event_types(before_commit_triggers_);
add_event_types(after_commit_triggers_);
return event_types;
}
} // namespace query

View File

@ -1,364 +1,23 @@
#pragma once
#include <algorithm>
#include <concepts>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <atomic>
#include <filesystem>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "kvstore/kvstore.hpp"
#include "query/cypher_query_interpreter.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/typed_value.hpp"
#include "query/trigger_context.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/concepts.hpp"
#include "utils/fnv.hpp"
#include "utils/skip_list.hpp"
#include "utils/spin_lock.hpp"
namespace 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 {{detail::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 always
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:
template <detail::ObjectAccessor TAccessor>
void RegisterCreatedObject(const TAccessor &created_object) {
GetRegistry<TAccessor>().created_objects_.emplace(created_object.Gid(), detail::CreatedObject{created_object});
}
template <detail::ObjectAccessor TAccessor>
void RegisterDeletedObject(const TAccessor &deleted_object) {
auto &registry = GetRegistry<TAccessor>();
if (registry.created_objects_.count(deleted_object.Gid())) {
return;
}
registry.deleted_objects_.emplace_back(deleted_object);
}
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.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());
}
void RegisterSetVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
void RegisterRemovedVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
[[nodiscard]] TriggerContext TransformToTriggerContext() &&;
private:
struct HashPair {
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, HashPair>;
template <detail::ObjectAccessor TAccessor>
using PropertyChangesLists = std::pair<std::vector<detail::SetObjectProperty<TAccessor>>,
std::vector<detail::RemovedObjectProperty<TAccessor>>>;
template <detail::ObjectAccessor TAccessor>
struct Registry {
using ChangesSummary =
std::tuple<std::vector<detail::CreatedObject<TAccessor>>, std::vector<detail::DeletedObject<TAccessor>>,
std::vector<detail::SetObjectProperty<TAccessor>>,
std::vector<detail::RemovedObjectProperty<TAccessor>>>;
[[nodiscard]] static PropertyChangesLists<TAccessor> PropertyMapToList(PropertyChangesMap<TAccessor> &&map) {
std::vector<detail::SetObjectProperty<TAccessor>> set_object_properties;
std::vector<detail::RemovedObjectProperty<TAccessor>> removed_object_properties;
for (auto it = map.begin(); it != map.end(); it = map.erase(it)) {
const auto &[key, property_change_info] = *it;
if (property_change_info.old_value.IsNull() && property_change_info.new_value.IsNull()) {
// no change happened on the transaction level
continue;
}
if (const auto is_equal = property_change_info.old_value == property_change_info.new_value;
is_equal.IsBool() && is_equal.ValueBool()) {
// no change happened on the transaction level
continue;
}
if (property_change_info.new_value.IsNull()) {
removed_object_properties.emplace_back(key.first, key.second /* property_id */,
std::move(property_change_info.old_value));
} else {
set_object_properties.emplace_back(key.first, key.second, std::move(property_change_info.old_value),
std::move(property_change_info.new_value));
}
}
return PropertyChangesLists<TAccessor>{std::move(set_object_properties), std::move(removed_object_properties)};
}
[[nodiscard]] ChangesSummary Summarize() && {
auto [set_object_properties, removed_object_properties] = PropertyMapToList(std::move(property_changes_));
std::vector<detail::CreatedObject<TAccessor>> created_objects_vec;
created_objects_vec.reserve(created_objects_.size());
std::transform(created_objects_.begin(), created_objects_.end(), std::back_inserter(created_objects_vec),
[](const auto &gid_and_created_object) { return gid_and_created_object.second; });
created_objects_.clear();
return {std::move(created_objects_vec), std::move(deleted_objects_), std::move(set_object_properties),
std::move(removed_object_properties)};
}
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_;
};
template <detail::ObjectAccessor TAccessor>
Registry<TAccessor> &GetRegistry() {
if constexpr (std::same_as<TAccessor, VertexAccessor>) {
return vertex_registry_;
} else {
return edge_registry_;
}
}
using LabelChangesMap = std::unordered_map<std::pair<VertexAccessor, storage::LabelId>, int8_t, HashPair>;
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_;
};
struct Trigger {
explicit Trigger(std::string name, const std::string &query,
const std::map<std::string, storage::PropertyValue> &user_parameters, TriggerEventType event_type,
@ -424,6 +83,7 @@ struct TriggerStore {
const auto &AfterCommitTriggers() const noexcept { return after_commit_triggers_; }
bool HasTriggers() const noexcept { return before_commit_triggers_.size() > 0 || after_commit_triggers_.size() > 0; }
std::unordered_set<TriggerEventType> GetEventTypes() const;
private:
utils::SpinLock store_lock_;

View File

@ -0,0 +1,549 @@
#include "query/trigger.hpp"
#include <concepts>
#include "query/context.hpp"
#include "query/cypher_query_interpreter.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/interpret/frame.hpp"
#include "query/serialization/property_value.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/memory.hpp"
namespace query {
namespace {
template <typename T>
concept WithToMap = requires(const T value, DbAccessor *dba) {
{ value.ToMap(dba) }
->std::same_as<std::map<std::string, TypedValue>>;
};
template <WithToMap T>
TypedValue ToTypedValue(const T &value, DbAccessor *dba) {
return TypedValue{value.ToMap(dba)};
}
template <detail::ObjectAccessor TAccessor>
TypedValue ToTypedValue(const detail::CreatedObject<TAccessor> &created_object, [[maybe_unused]] DbAccessor *dba) {
return TypedValue{created_object.object};
}
template <detail::ObjectAccessor TAccessor>
TypedValue ToTypedValue(const detail::DeletedObject<TAccessor> &deleted_object, [[maybe_unused]] DbAccessor *dba) {
return TypedValue{deleted_object.object};
}
template <typename T>
concept WithIsValid = requires(const T value) {
{ value.IsValid() }
->std::same_as<bool>;
};
template <typename T>
concept ConvertableToTypedValue = requires(T value, DbAccessor *dba) {
{ ToTypedValue(value, dba) }
->std::same_as<TypedValue>;
}
&&WithIsValid<T>;
template <typename T>
concept LabelUpdateContext = utils::SameAsAnyOf<T, detail::SetVertexLabel, detail::RemovedVertexLabel>;
template <LabelUpdateContext TContext>
TypedValue ToTypedValue(const std::vector<TContext> &values, DbAccessor *dba) {
std::unordered_map<storage::LabelId, std::vector<TypedValue>> vertices_by_labels;
for (const auto &value : values) {
if (value.IsValid()) {
vertices_by_labels[value.label_id].emplace_back(value.object);
}
}
TypedValue result{std::vector<TypedValue>{}};
auto &typed_values = result.ValueList();
for (auto &[label_id, vertices] : vertices_by_labels) {
typed_values.emplace_back(std::map<std::string, TypedValue>{
{std::string{"label"}, TypedValue(dba->LabelToName(label_id))},
{std::string{"vertices"}, TypedValue(std::move(vertices))},
});
}
return result;
}
template <ConvertableToTypedValue T>
TypedValue ToTypedValue(const std::vector<T> &values, DbAccessor *dba) requires(!LabelUpdateContext<T>) {
TypedValue result{std::vector<TypedValue>{}};
auto &typed_values = result.ValueList();
typed_values.reserve(values.size());
for (const auto &value : values) {
if (value.IsValid()) {
typed_values.push_back(ToTypedValue(value, dba));
}
}
return result;
}
template <typename T>
const char *TypeToString() {
if constexpr (std::same_as<T, detail::CreatedObject<VertexAccessor>>) {
return "created_vertex";
} else if constexpr (std::same_as<T, detail::CreatedObject<EdgeAccessor>>) {
return "created_edge";
} else if constexpr (std::same_as<T, detail::DeletedObject<VertexAccessor>>) {
return "deleted_vertex";
} else if constexpr (std::same_as<T, detail::DeletedObject<EdgeAccessor>>) {
return "deleted_edge";
} else if constexpr (std::same_as<T, detail::SetObjectProperty<VertexAccessor>>) {
return "set_vertex_property";
} else if constexpr (std::same_as<T, detail::SetObjectProperty<EdgeAccessor>>) {
return "set_edge_property";
} else if constexpr (std::same_as<T, detail::RemovedObjectProperty<VertexAccessor>>) {
return "removed_vertex_property";
} else if constexpr (std::same_as<T, detail::RemovedObjectProperty<EdgeAccessor>>) {
return "removed_edge_property";
} else if constexpr (std::same_as<T, detail::SetVertexLabel>) {
return "set_vertex_label";
} else if constexpr (std::same_as<T, detail::RemovedVertexLabel>) {
return "removed_vertex_label";
}
}
template <typename T>
concept ContextInfo = WithToMap<T> &&WithIsValid<T>;
template <ContextInfo... Args>
TypedValue Concatenate(DbAccessor *dba, const std::vector<Args> &...args) {
const auto size = (args.size() + ...);
TypedValue result{std::vector<TypedValue>{}};
auto &concatenated = result.ValueList();
concatenated.reserve(size);
const auto add_to_concatenated = [&]<ContextInfo T>(const std::vector<T> &values) {
for (const auto &value : values) {
if (value.IsValid()) {
auto map = value.ToMap(dba);
map["event_type"] = TypeToString<T>();
concatenated.emplace_back(std::move(map));
}
}
};
(add_to_concatenated(args), ...);
return result;
}
template <typename T>
concept WithEmpty = requires(const T value) {
{ value.empty() }
->std::same_as<bool>;
};
template <WithEmpty... TContainer>
bool AnyContainsValue(const TContainer &...value_containers) {
return (!value_containers.empty() || ...);
}
template <detail::ObjectAccessor TAccessor>
using ChangesSummary =
std::tuple<std::vector<detail::CreatedObject<TAccessor>>, std::vector<detail::DeletedObject<TAccessor>>,
std::vector<detail::SetObjectProperty<TAccessor>>,
std::vector<detail::RemovedObjectProperty<TAccessor>>>;
template <detail::ObjectAccessor TAccessor>
using PropertyChangesLists =
std::pair<std::vector<detail::SetObjectProperty<TAccessor>>, std::vector<detail::RemovedObjectProperty<TAccessor>>>;
template <detail::ObjectAccessor TAccessor>
[[nodiscard]] PropertyChangesLists<TAccessor> PropertyMapToList(
query::TriggerContextCollector::PropertyChangesMap<TAccessor> &&map) {
std::vector<detail::SetObjectProperty<TAccessor>> set_object_properties;
std::vector<detail::RemovedObjectProperty<TAccessor>> removed_object_properties;
for (auto it = map.begin(); it != map.end(); it = map.erase(it)) {
const auto &[key, property_change_info] = *it;
if (property_change_info.old_value.IsNull() && property_change_info.new_value.IsNull()) {
// no change happened on the transaction level
continue;
}
if (const auto is_equal = property_change_info.old_value == property_change_info.new_value;
is_equal.IsBool() && is_equal.ValueBool()) {
// no change happened on the transaction level
continue;
}
if (property_change_info.new_value.IsNull()) {
removed_object_properties.emplace_back(key.first, key.second /* property_id */,
std::move(property_change_info.old_value));
} else {
set_object_properties.emplace_back(key.first, key.second, std::move(property_change_info.old_value),
std::move(property_change_info.new_value));
}
}
return PropertyChangesLists<TAccessor>{std::move(set_object_properties), std::move(removed_object_properties)};
}
template <detail::ObjectAccessor TAccessor>
[[nodiscard]] ChangesSummary<TAccessor> Summarize(query::TriggerContextCollector::Registry<TAccessor> &&registry) {
auto [set_object_properties, removed_object_properties] = PropertyMapToList(std::move(registry.property_changes));
std::vector<detail::CreatedObject<TAccessor>> created_objects_vec;
created_objects_vec.reserve(registry.created_objects.size());
std::transform(registry.created_objects.begin(), registry.created_objects.end(),
std::back_inserter(created_objects_vec),
[](const auto &gid_and_created_object) { return gid_and_created_object.second; });
registry.created_objects.clear();
return {std::move(created_objects_vec), std::move(registry.deleted_objects), std::move(set_object_properties),
std::move(removed_object_properties)};
}
} // namespace
namespace detail {
bool SetVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> SetVertexLabel::ToMap(DbAccessor *dba) const {
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
}
bool RemovedVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
std::map<std::string, TypedValue> RemovedVertexLabel::ToMap(DbAccessor *dba) const {
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
}
} // namespace detail
const char *TriggerEventTypeToString(const TriggerEventType event_type) {
switch (event_type) {
case TriggerEventType::ANY:
return "ANY";
case TriggerEventType::CREATE:
return "CREATE";
case TriggerEventType::VERTEX_CREATE:
return "() CREATE";
case TriggerEventType::EDGE_CREATE:
return "--> CREATE";
case TriggerEventType::DELETE:
return "DELETE";
case TriggerEventType::VERTEX_DELETE:
return "() DELETE";
case TriggerEventType::EDGE_DELETE:
return "--> DELETE";
case TriggerEventType::UPDATE:
return "UPDATE";
case TriggerEventType::VERTEX_UPDATE:
return "() UPDATE";
case TriggerEventType::EDGE_UPDATE:
return "--> UPDATE";
}
}
void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
{
// adapt created_vertices_
auto it = created_vertices_.begin();
for (auto &created_vertex : created_vertices_) {
if (auto maybe_vertex = accessor->FindVertex(created_vertex.object.Gid(), storage::View::OLD); maybe_vertex) {
*it = detail::CreatedObject{*maybe_vertex};
++it;
}
}
created_vertices_.erase(it, created_vertices_.end());
}
// deleted_vertices_ should keep the transaction context of the transaction which deleted it
// because no other transaction can modify an object after it's deleted so it should be the
// latest state of the object
const auto adapt_context_with_vertex = [accessor](auto *values) {
auto it = values->begin();
for (auto &value : *values) {
if (auto maybe_vertex = accessor->FindVertex(value.object.Gid(), storage::View::OLD); maybe_vertex) {
*it = std::move(value);
it->object = *maybe_vertex;
++it;
}
}
values->erase(it, values->end());
};
adapt_context_with_vertex(&set_vertex_properties_);
adapt_context_with_vertex(&removed_vertex_properties_);
adapt_context_with_vertex(&set_vertex_labels_);
adapt_context_with_vertex(&removed_vertex_labels_);
{
// adapt created_edges
auto it = created_edges_.begin();
for (auto &created_edge : created_edges_) {
const auto maybe_from_vertex = accessor->FindVertex(created_edge.object.From().Gid(), storage::View::OLD);
if (!maybe_from_vertex) {
continue;
}
auto maybe_out_edges = maybe_from_vertex->OutEdges(storage::View::OLD);
MG_ASSERT(maybe_out_edges.HasValue());
const auto edge_gid = created_edge.object.Gid();
for (const auto &edge : *maybe_out_edges) {
if (edge.Gid() == edge_gid) {
*it = detail::CreatedObject{edge};
++it;
}
}
}
created_edges_.erase(it, created_edges_.end());
}
// deleted_edges_ should keep the transaction context of the transaction which deleted it
// because no other transaction can modify an object after it's deleted so it should be the
// latest state of the object
const auto adapt_context_with_edge = [accessor](auto *values) {
auto it = values->begin();
for (const auto &value : *values) {
if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::View::OLD); maybe_vertex) {
auto maybe_out_edges = maybe_vertex->OutEdges(storage::View::OLD);
MG_ASSERT(maybe_out_edges.HasValue());
for (const auto &edge : *maybe_out_edges) {
if (edge.Gid() == value.object.Gid()) {
*it = std::move(value);
it->object = edge;
++it;
break;
}
}
}
}
values->erase(it, values->end());
};
adapt_context_with_edge(&set_edge_properties_);
adapt_context_with_edge(&removed_edge_properties_);
}
TypedValue TriggerContext::GetTypedValue(const TriggerIdentifierTag tag, DbAccessor *dba) const {
switch (tag) {
case TriggerIdentifierTag::CREATED_VERTICES:
return ToTypedValue(created_vertices_, dba);
case TriggerIdentifierTag::CREATED_EDGES:
return ToTypedValue(created_edges_, dba);
case TriggerIdentifierTag::CREATED_OBJECTS:
return Concatenate(dba, created_vertices_, created_edges_);
case TriggerIdentifierTag::DELETED_VERTICES:
return ToTypedValue(deleted_vertices_, dba);
case TriggerIdentifierTag::DELETED_EDGES:
return ToTypedValue(deleted_edges_, dba);
case TriggerIdentifierTag::DELETED_OBJECTS:
return Concatenate(dba, deleted_vertices_, deleted_edges_);
case TriggerIdentifierTag::SET_VERTEX_PROPERTIES:
return ToTypedValue(set_vertex_properties_, dba);
case TriggerIdentifierTag::SET_EDGE_PROPERTIES:
return ToTypedValue(set_edge_properties_, dba);
case TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES:
return ToTypedValue(removed_vertex_properties_, dba);
case TriggerIdentifierTag::REMOVED_EDGE_PROPERTIES:
return ToTypedValue(removed_edge_properties_, dba);
case TriggerIdentifierTag::SET_VERTEX_LABELS:
return ToTypedValue(set_vertex_labels_, dba);
case TriggerIdentifierTag::REMOVED_VERTEX_LABELS:
return ToTypedValue(removed_vertex_labels_, dba);
case TriggerIdentifierTag::UPDATED_VERTICES:
return Concatenate(dba, set_vertex_properties_, removed_vertex_properties_, set_vertex_labels_,
removed_vertex_labels_);
case TriggerIdentifierTag::UPDATED_EDGES:
return Concatenate(dba, set_edge_properties_, removed_edge_properties_);
case TriggerIdentifierTag::UPDATED_OBJECTS:
return Concatenate(dba, set_vertex_properties_, set_edge_properties_, removed_vertex_properties_,
removed_edge_properties_, set_vertex_labels_, removed_vertex_labels_);
}
}
bool TriggerContext::ShouldEventTrigger(const TriggerEventType event_type) const {
using EventType = TriggerEventType;
switch (event_type) {
case EventType::ANY:
return AnyContainsValue(created_vertices_, created_edges_, deleted_vertices_, deleted_edges_,
set_vertex_properties_, set_edge_properties_, removed_vertex_properties_,
removed_edge_properties_, set_vertex_labels_, removed_vertex_labels_);
case EventType::CREATE:
return AnyContainsValue(created_vertices_, created_edges_);
case EventType::VERTEX_CREATE:
return AnyContainsValue(created_vertices_);
case EventType::EDGE_CREATE:
return AnyContainsValue(created_edges_);
case EventType::DELETE:
return AnyContainsValue(deleted_vertices_, deleted_edges_);
case EventType::VERTEX_DELETE:
return AnyContainsValue(deleted_vertices_);
case EventType::EDGE_DELETE:
return AnyContainsValue(deleted_edges_);
case EventType::UPDATE:
return AnyContainsValue(set_vertex_properties_, set_edge_properties_, removed_vertex_properties_,
removed_edge_properties_, set_vertex_labels_, removed_vertex_labels_);
case EventType::VERTEX_UPDATE:
return AnyContainsValue(set_vertex_properties_, removed_vertex_properties_, set_vertex_labels_,
removed_vertex_labels_);
case EventType::EDGE_UPDATE:
return AnyContainsValue(set_edge_properties_, removed_edge_properties_);
}
}
void TriggerContextCollector::UpdateLabelMap(const VertexAccessor vertex, const storage::LabelId label_id,
const LabelChange change) {
auto &registry = GetRegistry<VertexAccessor>();
if (!registry.should_register_updated_objects || registry.created_objects.count(vertex.Gid())) {
return;
}
if (auto it = label_changes_.find({vertex, label_id}); it != label_changes_.end()) {
it->second = std::clamp(it->second + LabelChangeToInt(change), -1, 1);
return;
}
label_changes_.emplace(std::make_pair(vertex, label_id), LabelChangeToInt(change));
}
TriggerContextCollector::TriggerContextCollector(const std::unordered_set<TriggerEventType> &event_types) {
for (const auto event_type : event_types) {
switch (event_type) {
case TriggerEventType::ANY:
vertex_registry_.should_register_created_objects = true;
edge_registry_.should_register_created_objects = true;
vertex_registry_.should_register_deleted_objects = true;
edge_registry_.should_register_deleted_objects = true;
vertex_registry_.should_register_updated_objects = true;
edge_registry_.should_register_updated_objects = true;
break;
case TriggerEventType::VERTEX_CREATE:
vertex_registry_.should_register_created_objects = true;
break;
case TriggerEventType::EDGE_CREATE:
edge_registry_.should_register_created_objects = true;
break;
case TriggerEventType::CREATE:
vertex_registry_.should_register_created_objects = true;
edge_registry_.should_register_created_objects = true;
break;
case TriggerEventType::VERTEX_DELETE:
vertex_registry_.should_register_deleted_objects = true;
break;
case TriggerEventType::EDGE_DELETE:
edge_registry_.should_register_deleted_objects = true;
break;
case TriggerEventType::DELETE:
vertex_registry_.should_register_deleted_objects = true;
edge_registry_.should_register_deleted_objects = true;
break;
case TriggerEventType::VERTEX_UPDATE:
vertex_registry_.should_register_updated_objects = true;
break;
case TriggerEventType::EDGE_UPDATE:
edge_registry_.should_register_updated_objects = true;
break;
case TriggerEventType::UPDATE:
vertex_registry_.should_register_updated_objects = true;
edge_registry_.should_register_updated_objects = true;
break;
}
}
const auto deduce_if_should_register_created = [](auto &registry) {
// Registering the created objects is necessary to:
// - eliminate deleted objects that were created in the same transaction
// - eliminate set/removed properties and labels of newly created objects
// because those changes are only relevant for objects that have existed before the transaction.
registry.should_register_created_objects |=
registry.should_register_updated_objects || registry.should_register_deleted_objects;
};
deduce_if_should_register_created(vertex_registry_);
deduce_if_should_register_created(edge_registry_);
}
bool TriggerContextCollector::ShouldRegisterVertexLabelChange() const {
return vertex_registry_.should_register_updated_objects;
}
void TriggerContextCollector::RegisterSetVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id) {
UpdateLabelMap(vertex, label_id, LabelChange::ADD);
}
void TriggerContextCollector::RegisterRemovedVertexLabel(const VertexAccessor &vertex,
const storage::LabelId label_id) {
UpdateLabelMap(vertex, label_id, LabelChange::REMOVE);
}
int8_t TriggerContextCollector::LabelChangeToInt(LabelChange change) {
static_assert(std::is_same_v<std::underlying_type_t<LabelChange>, int8_t>,
"The underlying type of LabelChange doesn't match the return type!");
return static_cast<int8_t>(change);
}
TriggerContext TriggerContextCollector::TransformToTriggerContext() && {
auto [created_vertices, deleted_vertices, set_vertex_properties, removed_vertex_properties] =
Summarize(std::move(vertex_registry_));
auto [set_vertex_labels, removed_vertex_labels] = LabelMapToList(std::move(label_changes_));
auto [created_edges, deleted_edges, set_edge_properties, removed_edge_properties] =
Summarize(std::move(edge_registry_));
return {std::move(created_vertices), std::move(deleted_vertices),
std::move(set_vertex_properties), std::move(removed_vertex_properties),
std::move(set_vertex_labels), std::move(removed_vertex_labels),
std::move(created_edges), std::move(deleted_edges),
std::move(set_edge_properties), std::move(removed_edge_properties)};
}
TriggerContextCollector::LabelChangesLists TriggerContextCollector::LabelMapToList(LabelChangesMap &&label_changes) {
std::vector<detail::SetVertexLabel> set_vertex_labels;
std::vector<detail::RemovedVertexLabel> removed_vertex_labels;
for (const auto &[key, label_state] : label_changes) {
if (label_state == LabelChangeToInt(LabelChange::ADD)) {
set_vertex_labels.emplace_back(key.first, key.second);
} else if (label_state == LabelChangeToInt(LabelChange::REMOVE)) {
removed_vertex_labels.emplace_back(key.first, key.second);
}
}
label_changes.clear();
return {std::move(set_vertex_labels), std::move(removed_vertex_labels)};
}
} // namespace query

View File

@ -0,0 +1,353 @@
#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 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 query

View File

@ -105,7 +105,7 @@ void CreateOnUpdateTriggers(mg::Client &client, bool is_before) {
fmt::format("CREATE TRIGGER SetVertexPropertiesTrigger ON () UPDATE "
"{} COMMIT "
"EXECUTE "
"UNWIND assignedVertexProperties as assignedVertexProperty "
"UNWIND setVertexProperties as assignedVertexProperty "
"CREATE (n: {} {{ id: assignedVertexProperty.vertex.id }})",
before_or_after, kTriggerSetVertexPropertyLabel));
client.DiscardAll();
@ -121,7 +121,7 @@ void CreateOnUpdateTriggers(mg::Client &client, bool is_before) {
fmt::format("CREATE TRIGGER SetVertexLabelsTrigger ON () UPDATE "
"{} COMMIT "
"EXECUTE "
"UNWIND assignedVertexLabels as assignedVertexLabel "
"UNWIND setVertexLabels as assignedVertexLabel "
"UNWIND assignedVertexLabel.vertices as vertex "
"CREATE (n: {} {{ id: vertex.id }})",
before_or_after, kTriggerSetVertexLabelLabel));
@ -140,7 +140,7 @@ void CreateOnUpdateTriggers(mg::Client &client, bool is_before) {
fmt::format("CREATE TRIGGER SetEdgePropertiesTrigger ON --> UPDATE "
"{} COMMIT "
"EXECUTE "
"UNWIND assignedEdgeProperties as assignedEdgeProperty "
"UNWIND setEdgeProperties as assignedEdgeProperty "
"CREATE (n: {} {{ id: assignedEdgeProperty.edge.id }})",
before_or_after, kTriggerSetEdgePropertyLabel));
client.DiscardAll();

View File

@ -1,12 +1,22 @@
#include <gtest/gtest.h>
#include <filesystem>
#include <fmt/format.h>
#include "query/db_accessor.hpp"
#include "query/interpreter.hpp"
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "utils/memory.hpp"
namespace {
const std::unordered_set<query::TriggerEventType> kAllEventTypes{
query::TriggerEventType::ANY, query::TriggerEventType::VERTEX_CREATE, query::TriggerEventType::EDGE_CREATE,
query::TriggerEventType::CREATE, query::TriggerEventType::VERTEX_DELETE, query::TriggerEventType::EDGE_DELETE,
query::TriggerEventType::DELETE, query::TriggerEventType::VERTEX_UPDATE, query::TriggerEventType::EDGE_UPDATE,
query::TriggerEventType::UPDATE,
};
} // namespace
class TriggerContextTest : public ::testing::Test {
public:
void SetUp() override { db.emplace(); }
@ -31,7 +41,7 @@ void CheckTypedValueSize(const query::TriggerContext &trigger_context, const que
const size_t expected_size, query::DbAccessor &dba) {
auto typed_values = trigger_context.GetTypedValue(tag, &dba);
ASSERT_TRUE(typed_values.IsList());
ASSERT_EQ(typed_values.ValueList().size(), expected_size);
ASSERT_EQ(expected_size, typed_values.ValueList().size());
};
void CheckLabelList(const query::TriggerContext &trigger_context, const query::TriggerIdentifierTag tag,
@ -61,7 +71,7 @@ void CheckLabelList(const query::TriggerContext &trigger_context, const query::T
// that exist (unless its explicitly created for the deleted object)
TEST_F(TriggerContextTest, ValidObjectsTest) {
query::TriggerContext trigger_context;
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{kAllEventTypes};
size_t vertex_count = 0;
size_t edge_count = 0;
@ -95,7 +105,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) {
dba.AdvanceCommand();
trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
trigger_context_collector = query::TriggerContextCollector{};
trigger_context_collector = query::TriggerContextCollector{kAllEventTypes};
// Should have all the created objects
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_VERTICES, vertex_count, dba);
@ -181,7 +191,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) {
ASSERT_FALSE(dba.Commit().HasError());
trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
trigger_context_collector = query::TriggerContextCollector{};
trigger_context_collector = query::TriggerContextCollector{kAllEventTypes};
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_VERTEX_PROPERTIES, vertex_count, dba);
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_EDGE_PROPERTIES, edge_count, dba);
@ -250,7 +260,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) {
// Binding the trigger context to transaction will mean that creating and updating an object in the same transaction
// will return only the CREATE event.
TEST_F(TriggerContextTest, ReturnCreateOnlyEvent) {
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{kAllEventTypes};
query::DbAccessor dba{&StartTransaction()};
@ -311,13 +321,14 @@ void EXPECT_PROP_EQ(const query::TypedValue &a, const query::TypedValue &b) { EX
// transaction) everything inbetween should be ignored.
TEST_F(TriggerContextTest, GlobalPropertyChange) {
query::DbAccessor dba{&StartTransaction()};
const std::unordered_set<query::TriggerEventType> event_types{query::TriggerEventType::VERTEX_UPDATE};
auto v = dba.InsertVertex();
dba.AdvanceCommand();
{
SPDLOG_DEBUG("SET -> SET");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
query::TypedValue("ValueNew"));
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"),
@ -339,7 +350,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("SET -> REMOVE");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
query::TypedValue("ValueNew"));
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
@ -360,7 +371,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("REMOVE -> SET");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
query::TypedValue("Value"));
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
@ -382,7 +393,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("REMOVE -> REMOVE");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
query::TypedValue("Value"));
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue());
@ -402,7 +413,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("SET -> SET (no change on transaction level)");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
query::TypedValue("ValueNew"));
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"),
@ -416,7 +427,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("SET -> REMOVE (no change on transaction level)");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
query::TypedValue("ValueNew"));
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
@ -430,7 +441,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("REMOVE -> SET (no change on transaction level)");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
query::TypedValue("Value"));
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
@ -444,7 +455,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("REMOVE -> REMOVE (no change on transaction level)");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue());
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue());
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
@ -456,7 +467,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
{
SPDLOG_DEBUG("SET -> REMOVE -> SET -> REMOVE -> SET");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value0"),
query::TypedValue("Value1"));
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
@ -486,6 +497,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
// Same as above, but for label changes
TEST_F(TriggerContextTest, GlobalLabelChange) {
query::DbAccessor dba{&StartTransaction()};
const std::unordered_set<query::TriggerEventType> event_types{query::TriggerEventType::VERTEX_UPDATE};
auto v = dba.InsertVertex();
dba.AdvanceCommand();
@ -495,7 +507,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
// so REMOVE -> REMOVE and SET -> SET doesn't make sense
{
SPDLOG_DEBUG("SET -> REMOVE");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
@ -507,7 +519,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
{
SPDLOG_DEBUG("REMOVE -> SET");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
@ -519,7 +531,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
{
SPDLOG_DEBUG("SET -> REMOVE -> SET -> REMOVE -> SET");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
@ -540,7 +552,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
{
SPDLOG_DEBUG("REMOVE -> SET -> REMOVE -> SET -> REMOVE");
query::TriggerContextCollector trigger_context_collector;
query::TriggerContextCollector trigger_context_collector{event_types};
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
@ -560,6 +572,231 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
}
}
namespace {
struct ShouldRegisterExpectation {
bool creation{false};
bool deletion{false};
bool update{false};
};
template <typename TAccessor>
void CheckRegisterInfo(const query::TriggerContextCollector &collector, const ShouldRegisterExpectation &expectation) {
EXPECT_EQ(expectation.creation, collector.ShouldRegisterCreatedObject<TAccessor>());
EXPECT_EQ(expectation.deletion, collector.ShouldRegisterDeletedObject<TAccessor>());
EXPECT_EQ(expectation.update, collector.ShouldRegisterObjectPropertyChange<TAccessor>());
}
size_t BoolToSize(const bool value) { return value ? 1 : 0; }
void CheckFilters(const std::unordered_set<query::TriggerEventType> &event_types,
const ShouldRegisterExpectation &vertex_expectation,
const ShouldRegisterExpectation &edge_expectation, storage::Storage::Accessor *accessor) {
query::TriggerContextCollector collector{event_types};
{
SCOPED_TRACE("Checking vertex");
CheckRegisterInfo<query::VertexAccessor>(collector, vertex_expectation);
}
{
SCOPED_TRACE("Checking edge");
CheckRegisterInfo<query::EdgeAccessor>(collector, edge_expectation);
}
EXPECT_EQ(collector.ShouldRegisterVertexLabelChange(), vertex_expectation.update);
query::DbAccessor dba{accessor};
auto vertex_to_delete = dba.InsertVertex();
auto vertex_to_modify = dba.InsertVertex();
auto from_vertex = dba.InsertVertex();
auto to_vertex = dba.InsertVertex();
auto maybe_edge_to_delete = dba.InsertEdge(&from_vertex, &to_vertex, dba.NameToEdgeType("EDGE"));
auto maybe_edge_to_modify = dba.InsertEdge(&from_vertex, &to_vertex, dba.NameToEdgeType("EDGE"));
auto &edge_to_delete = maybe_edge_to_delete.GetValue();
auto &edge_to_modify = maybe_edge_to_modify.GetValue();
dba.AdvanceCommand();
const auto created_vertex = dba.InsertVertex();
const auto maybe_created_edge = dba.InsertEdge(&from_vertex, &to_vertex, dba.NameToEdgeType("EDGE"));
const auto created_edge = maybe_created_edge.GetValue();
collector.RegisterCreatedObject(created_vertex);
collector.RegisterCreatedObject(created_edge);
collector.RegisterDeletedObject(dba.RemoveEdge(&edge_to_delete).GetValue().value());
collector.RegisterDeletedObject(dba.RemoveVertex(&vertex_to_delete).GetValue().value());
collector.RegisterSetObjectProperty(vertex_to_modify, dba.NameToProperty("UPDATE"), query::TypedValue{1},
query::TypedValue{2});
collector.RegisterRemovedObjectProperty(vertex_to_modify, dba.NameToProperty("REMOVE"), query::TypedValue{1});
collector.RegisterSetObjectProperty(edge_to_modify, dba.NameToProperty("UPDATE"), query::TypedValue{1},
query::TypedValue{2});
collector.RegisterRemovedObjectProperty(edge_to_modify, dba.NameToProperty("REMOVE"), query::TypedValue{1});
collector.RegisterSetVertexLabel(vertex_to_modify, dba.NameToLabel("SET"));
collector.RegisterRemovedVertexLabel(vertex_to_modify, dba.NameToLabel("REMOVE"));
dba.AdvanceCommand();
const auto trigger_context = std::move(collector).TransformToTriggerContext();
const auto created_vertices = BoolToSize(vertex_expectation.creation);
{
SCOPED_TRACE("CREATED_VERTICES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_VERTICES, created_vertices, dba);
}
const auto created_edges = BoolToSize(edge_expectation.creation);
{
SCOPED_TRACE("CREATED_EDGES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_EDGES, created_edges, dba);
}
{
SCOPED_TRACE("CREATED_OBJECTS");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_OBJECTS, created_vertices + created_edges,
dba);
}
const auto deleted_vertices = BoolToSize(vertex_expectation.deletion);
{
SCOPED_TRACE("DELETED_VERTICES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::DELETED_VERTICES, deleted_vertices, dba);
}
const auto deleted_edges = BoolToSize(edge_expectation.deletion);
{
SCOPED_TRACE("DELETED_EDGES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::DELETED_EDGES, deleted_edges, dba);
}
{
SCOPED_TRACE("DELETED_OBJECTS");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::DELETED_OBJECTS, deleted_vertices + deleted_edges,
dba);
}
{
SCOPED_TRACE("SET_VERTEX_PROPERTIES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_VERTEX_PROPERTIES,
BoolToSize(vertex_expectation.update), dba);
}
{
SCOPED_TRACE("SET_EDGE_PROPERTIES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_EDGE_PROPERTIES,
BoolToSize(edge_expectation.update), dba);
}
{
SCOPED_TRACE("REMOVED_VERTEX_PROPERTIES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES,
BoolToSize(vertex_expectation.update), dba);
}
{
SCOPED_TRACE("REMOVED_EDGE_PROPERTIES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::REMOVED_EDGE_PROPERTIES,
BoolToSize(edge_expectation.update), dba);
}
const auto set_and_removed_vertex_props_and_labels = BoolToSize(vertex_expectation.update) * 4;
{
SCOPED_TRACE("UPDATED_VERTICES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::UPDATED_VERTICES,
set_and_removed_vertex_props_and_labels, dba);
}
const auto set_and_removed_edge_props = BoolToSize(edge_expectation.update) * 2;
{
SCOPED_TRACE("UPDATED_EDGES");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::UPDATED_EDGES, set_and_removed_edge_props, dba);
}
// sum of the previous
{
SCOPED_TRACE("UPDATED_OBJECTS");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::UPDATED_OBJECTS,
set_and_removed_vertex_props_and_labels + set_and_removed_edge_props, dba);
}
{
SCOPED_TRACE("SET_VERTEX_LABELS");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_VERTEX_LABELS,
BoolToSize(vertex_expectation.update), dba);
}
{
SCOPED_TRACE("REMOVED_VERTEX_LABELS");
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::REMOVED_VERTEX_LABELS,
BoolToSize(vertex_expectation.update), dba);
}
dba.Abort();
}
} // namespace
TEST_F(TriggerContextTest, Filtering) {
using TET = query::TriggerEventType;
// Check all event type individually
{
SCOPED_TRACE("TET::ANY");
CheckFilters({TET::ANY}, ShouldRegisterExpectation{true, true, true}, ShouldRegisterExpectation{true, true, true},
&StartTransaction());
}
{
SCOPED_TRACE("TET::VERTEX_CREATE");
CheckFilters({TET::VERTEX_CREATE}, ShouldRegisterExpectation{true, false, false},
ShouldRegisterExpectation{false, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::EDGE_CREATE");
CheckFilters({TET::EDGE_CREATE}, ShouldRegisterExpectation{false, false, false},
ShouldRegisterExpectation{true, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::CREATE");
CheckFilters({TET::CREATE}, ShouldRegisterExpectation{true, false, false},
ShouldRegisterExpectation{true, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::VERTEX_DELETE");
CheckFilters({TET::VERTEX_DELETE}, ShouldRegisterExpectation{true, true, false},
ShouldRegisterExpectation{false, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::EDGE_DELETE");
CheckFilters({TET::EDGE_DELETE}, ShouldRegisterExpectation{false, false, false},
ShouldRegisterExpectation{true, true, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::DELETE");
CheckFilters({TET::DELETE}, ShouldRegisterExpectation{true, true, false},
ShouldRegisterExpectation{true, true, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::VERTEX_UPDATE");
CheckFilters({TET::VERTEX_UPDATE}, ShouldRegisterExpectation{true, false, true},
ShouldRegisterExpectation{false, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::EDGE_UPDATE");
CheckFilters({TET::EDGE_UPDATE}, ShouldRegisterExpectation{false, false, false},
ShouldRegisterExpectation{true, false, true}, &StartTransaction());
}
{
SCOPED_TRACE("TET::UPDATE");
CheckFilters({TET::UPDATE}, ShouldRegisterExpectation{true, false, true},
ShouldRegisterExpectation{true, false, true}, &StartTransaction());
}
// Some combined versions
{
SCOPED_TRACE("TET::VERTEX_UPDATE, TET::EDGE_UPDATE");
CheckFilters({TET::VERTEX_UPDATE, TET::EDGE_UPDATE}, ShouldRegisterExpectation{true, false, true},
ShouldRegisterExpectation{true, false, true}, &StartTransaction());
}
{
SCOPED_TRACE("TET::VERTEX_UPDATE, TET::EDGE_UPDATE, TET::DELETE");
CheckFilters({TET::VERTEX_UPDATE, TET::EDGE_UPDATE, TET::DELETE}, ShouldRegisterExpectation{true, true, true},
ShouldRegisterExpectation{true, true, true}, &StartTransaction());
}
{
SCOPED_TRACE("TET::UPDATE, TET::VERTEX_DELETE, TET::EDGE_DELETE");
CheckFilters({TET::UPDATE, TET::VERTEX_DELETE, TET::EDGE_DELETE}, ShouldRegisterExpectation{true, true, true},
ShouldRegisterExpectation{true, true, true}, &StartTransaction());
}
{
SCOPED_TRACE("TET::VERTEX_CREATE, TET::VERTEX_UPDATE");
CheckFilters({TET::VERTEX_CREATE, TET::VERTEX_UPDATE}, ShouldRegisterExpectation{true, false, true},
ShouldRegisterExpectation{false, false, false}, &StartTransaction());
}
{
SCOPED_TRACE("TET::EDGE_CREATE, TET::EDGE_UPDATE");
CheckFilters({TET::EDGE_CREATE, TET::EDGE_UPDATE}, ShouldRegisterExpectation{false, false, false},
ShouldRegisterExpectation{true, false, true}, &StartTransaction());
}
}
class TriggerStoreTest : public ::testing::Test {
protected:
const std::filesystem::path testing_directory{std::filesystem::temp_directory_path() / "MG_test_unit_query_trigger"};
@ -746,3 +983,24 @@ TEST_F(TriggerStoreTest, TriggerInfo) {
check_trigger_info();
}
TEST_F(TriggerStoreTest, AnyTriggerAllKeywords) {
query::TriggerStore store{testing_directory, &ast_cache, &*dba, &antlr_lock};
using namespace std::literals;
const std::array keywords = {
"createdVertices"sv, "createdEdges"sv, "createdObjects"sv,
"deletedVertices"sv, "deletedEdges"sv, "deletedObjects"sv,
"setVertexProperties"sv, "setEdgeProperties"sv, "removedVertexProperties"sv,
"removedEdgeProperties"sv, "setVertexLabels"sv, "removedVertexLabels"sv,
"updatedVertices"sv, "updatedEdges"sv, "updatedObjects"sv,
};
const auto trigger_name = "trigger"s;
for (const auto keyword : keywords) {
ASSERT_NO_THROW(store.AddTrigger(trigger_name, fmt::format("RETURN {}", keyword), {}, query::TriggerEventType::ANY,
query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock));
store.DropTrigger(trigger_name);
}
}