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:
parent
1abee1ed3a
commit
e8a1d15a55
@ -4,6 +4,7 @@
|
||||
|
||||
### Major Feature and Improvements
|
||||
|
||||
* Added triggers.
|
||||
* Replaced mg_client with mgconsole
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -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})
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 ®istry = 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
|
||||
|
@ -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 ®istry = 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 ®istry = 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_;
|
||||
|
549
src/query/trigger_context.cpp
Normal file
549
src/query/trigger_context.cpp
Normal 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> &®istry) {
|
||||
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 ®istry = 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 ®istry) {
|
||||
// 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
|
353
src/query/trigger_context.hpp
Normal file
353
src/query/trigger_context.hpp
Normal 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 ®istry = 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 ®istry = 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 ®istry = 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
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user