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
|
### Major Feature and Improvements
|
||||||
|
|
||||||
|
* Added triggers.
|
||||||
* Replaced mg_client with mgconsole
|
* Replaced mg_client with mgconsole
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
@ -33,6 +33,7 @@ set(mg_query_sources
|
|||||||
procedure/py_module.cpp
|
procedure/py_module.cpp
|
||||||
serialization/property_value.cpp
|
serialization/property_value.cpp
|
||||||
trigger.cpp
|
trigger.cpp
|
||||||
|
trigger_context.cpp
|
||||||
typed_value.cpp)
|
typed_value.cpp)
|
||||||
|
|
||||||
add_library(mg-query STATIC ${mg_query_sources})
|
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) {
|
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
|
||||||
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
|
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) {
|
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());
|
execution_db_accessor_.emplace(db_accessor_.get());
|
||||||
|
|
||||||
if (interpreter_context_->trigger_store->HasTriggers()) {
|
if (interpreter_context_->trigger_store->HasTriggers()) {
|
||||||
trigger_context_collector_.emplace();
|
trigger_context_collector_.emplace(interpreter_context_->trigger_store->GetEventTypes());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (query_upper == "COMMIT") {
|
} 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());
|
execution_db_accessor_.emplace(db_accessor_.get());
|
||||||
|
|
||||||
if (utils::Downcast<CypherQuery>(parsed_query.query) && interpreter_context_->trigger_store->HasTriggers()) {
|
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;
|
std::optional<TriggerContext> trigger_context = std::nullopt;
|
||||||
if (trigger_context_collector_) {
|
if (trigger_context_collector_) {
|
||||||
trigger_context.emplace(std::move(*trigger_context_collector_).TransformToTriggerContext());
|
trigger_context.emplace(std::move(*trigger_context_collector_).TransformToTriggerContext());
|
||||||
|
trigger_context_collector_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trigger_context) {
|
if (trigger_context) {
|
||||||
@ -1667,6 +1616,12 @@ void Interpreter::Commit() {
|
|||||||
SPDLOG_DEBUG("Finished executing before commit triggers");
|
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();
|
auto maybe_constraint_violation = db_accessor_->Commit();
|
||||||
if (maybe_constraint_violation.HasError()) {
|
if (maybe_constraint_violation.HasError()) {
|
||||||
const auto &constraint_violation = maybe_constraint_violation.GetError();
|
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);
|
auto label_name = execution_db_accessor_->LabelToName(constraint_violation.label);
|
||||||
MG_ASSERT(constraint_violation.properties.size() == 1U);
|
MG_ASSERT(constraint_violation.properties.size() == 1U);
|
||||||
auto property_name = execution_db_accessor_->PropertyToName(*constraint_violation.properties.begin());
|
auto property_name = execution_db_accessor_->PropertyToName(*constraint_violation.properties.begin());
|
||||||
execution_db_accessor_.reset();
|
reset_necessary_members();
|
||||||
db_accessor_.reset();
|
|
||||||
trigger_context_collector_.reset();
|
|
||||||
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
|
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
|
||||||
property_name);
|
property_name);
|
||||||
break;
|
break;
|
||||||
@ -1688,9 +1641,7 @@ void Interpreter::Commit() {
|
|||||||
utils::PrintIterable(
|
utils::PrintIterable(
|
||||||
property_names_stream, constraint_violation.properties, ", ",
|
property_names_stream, constraint_violation.properties, ", ",
|
||||||
[this](auto &stream, const auto &prop) { stream << execution_db_accessor_->PropertyToName(prop); });
|
[this](auto &stream, const auto &prop) { stream << execution_db_accessor_->PropertyToName(prop); });
|
||||||
execution_db_accessor_.reset();
|
reset_necessary_members();
|
||||||
db_accessor_.reset();
|
|
||||||
trigger_context_collector_.reset();
|
|
||||||
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
|
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
|
||||||
property_names_stream.str());
|
property_names_stream.str());
|
||||||
break;
|
break;
|
||||||
@ -1714,9 +1665,7 @@ void Interpreter::Commit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
execution_db_accessor_.reset();
|
reset_necessary_members();
|
||||||
db_accessor_.reset();
|
|
||||||
trigger_context_collector_.reset();
|
|
||||||
|
|
||||||
SPDLOG_DEBUG("Finished comitting the transaction");
|
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.");
|
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);
|
context.trigger_context_collector->RegisterDeletedObject(res.GetValue()->first);
|
||||||
for (const auto &deleted_edge : res.GetValue()->second) {
|
for (const auto &deleted_edge : res.GetValue()->second) {
|
||||||
context.trigger_context_collector->RegisterDeletedObject(deleted_edge);
|
context.trigger_context_collector->RegisterDeletedObject(deleted_edge);
|
||||||
@ -2025,6 +2026,9 @@ template <AccessorWithProperties TRecordAccessor>
|
|||||||
void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op,
|
void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op,
|
||||||
ExecutionContext *context) {
|
ExecutionContext *context) {
|
||||||
std::optional<std::map<storage::PropertyId, storage::PropertyValue>> old_values;
|
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) {
|
if (op == SetProperties::Op::REPLACE) {
|
||||||
auto maybe_value = record->ClearProperties();
|
auto maybe_value = record->ClearProperties();
|
||||||
if (maybe_value.HasError()) {
|
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));
|
old_values.emplace(std::move(*maybe_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2063,10 +2067,10 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
|||||||
return *maybe_props;
|
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 {
|
auto old_value = [&]() -> storage::PropertyValue {
|
||||||
if (!old_values) {
|
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()) {
|
if (auto it = old_values->find(key); it != old_values->end()) {
|
||||||
@ -2075,8 +2079,9 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
|||||||
|
|
||||||
return {};
|
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) {
|
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));
|
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()) {
|
for (const auto &kv : rhs.ValueMap()) {
|
||||||
auto key = context->db_accessor->NameToProperty(kv.first);
|
auto key = context->db_accessor->NameToProperty(kv.first);
|
||||||
auto old_value = PropsSetChecked(record, key, kv.second);
|
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);
|
register_set_property(std::move(old_value), key, kv.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2125,7 +2130,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
|||||||
"map.");
|
"map.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context->trigger_context_collector && old_values) {
|
if (should_register_change && old_values) {
|
||||||
// register removed properties
|
// register removed properties
|
||||||
for (auto &[property_id, property_value] : *old_values) {
|
for (auto &[property_id, property_value] : *old_values) {
|
||||||
context->trigger_context_collector->RegisterRemovedObjectProperty(*record, property_id,
|
context->trigger_context_collector->RegisterRemovedObjectProperty(*record, property_id,
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
namespace query {
|
namespace query {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
|
auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TriggerIdentifierTag::CREATED_VERTICES:
|
case TriggerIdentifierTag::CREATED_VERTICES:
|
||||||
@ -36,10 +35,10 @@ auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
|
|||||||
return "deletedObjects";
|
return "deletedObjects";
|
||||||
|
|
||||||
case TriggerIdentifierTag::SET_VERTEX_PROPERTIES:
|
case TriggerIdentifierTag::SET_VERTEX_PROPERTIES:
|
||||||
return "assignedVertexProperties";
|
return "setVertexProperties";
|
||||||
|
|
||||||
case TriggerIdentifierTag::SET_EDGE_PROPERTIES:
|
case TriggerIdentifierTag::SET_EDGE_PROPERTIES:
|
||||||
return "assignedEdgeProperties";
|
return "setEdgeProperties";
|
||||||
|
|
||||||
case TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES:
|
case TriggerIdentifierTag::REMOVED_VERTEX_PROPERTIES:
|
||||||
return "removedVertexProperties";
|
return "removedVertexProperties";
|
||||||
@ -48,7 +47,7 @@ auto IdentifierString(const TriggerIdentifierTag tag) noexcept {
|
|||||||
return "removedEdgeProperties";
|
return "removedEdgeProperties";
|
||||||
|
|
||||||
case TriggerIdentifierTag::SET_VERTEX_LABELS:
|
case TriggerIdentifierTag::SET_VERTEX_LABELS:
|
||||||
return "assignedVertexLabels";
|
return "setVertexLabels";
|
||||||
|
|
||||||
case TriggerIdentifierTag::REMOVED_VERTEX_LABELS:
|
case TriggerIdentifierTag::REMOVED_VERTEX_LABELS:
|
||||||
return "removedVertexLabels";
|
return "removedVertexLabels";
|
||||||
@ -87,7 +86,13 @@ std::vector<std::pair<Identifier, TriggerIdentifierTag>> GetPredefinedIdentifier
|
|||||||
|
|
||||||
switch (event_type) {
|
switch (event_type) {
|
||||||
case EventType::ANY:
|
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:
|
case EventType::CREATE:
|
||||||
return TagsToIdentifiers(IdentifierTag::CREATED_OBJECTS);
|
return TagsToIdentifiers(IdentifierTag::CREATED_OBJECTS);
|
||||||
@ -120,420 +125,7 @@ std::vector<std::pair<Identifier, TriggerIdentifierTag>> GetPredefinedIdentifier
|
|||||||
IdentifierTag::UPDATED_EDGES);
|
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
|
||||||
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,
|
Trigger::Trigger(std::string name, const std::string &query,
|
||||||
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||||
@ -777,4 +369,18 @@ std::vector<TriggerStore::TriggerInfo> TriggerStore::GetTriggerInfo() const {
|
|||||||
|
|
||||||
return info;
|
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
|
} // namespace query
|
||||||
|
@ -1,364 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <algorithm>
|
|
||||||
#include <concepts>
|
#include <atomic>
|
||||||
#include <iterator>
|
#include <filesystem>
|
||||||
#include <tuple>
|
#include <map>
|
||||||
#include <type_traits>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "kvstore/kvstore.hpp"
|
#include "kvstore/kvstore.hpp"
|
||||||
#include "query/cypher_query_interpreter.hpp"
|
#include "query/cypher_query_interpreter.hpp"
|
||||||
|
#include "query/db_accessor.hpp"
|
||||||
#include "query/frontend/ast/ast.hpp"
|
#include "query/frontend/ast/ast.hpp"
|
||||||
#include "query/typed_value.hpp"
|
#include "query/trigger_context.hpp"
|
||||||
#include "storage/v2/property_value.hpp"
|
#include "storage/v2/property_value.hpp"
|
||||||
#include "utils/concepts.hpp"
|
#include "utils/skip_list.hpp"
|
||||||
#include "utils/fnv.hpp"
|
#include "utils/spin_lock.hpp"
|
||||||
|
|
||||||
namespace query {
|
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 {
|
struct Trigger {
|
||||||
explicit Trigger(std::string name, const std::string &query,
|
explicit Trigger(std::string name, const std::string &query,
|
||||||
const std::map<std::string, storage::PropertyValue> &user_parameters, TriggerEventType event_type,
|
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_; }
|
const auto &AfterCommitTriggers() const noexcept { return after_commit_triggers_; }
|
||||||
|
|
||||||
bool HasTriggers() const noexcept { return before_commit_triggers_.size() > 0 || after_commit_triggers_.size() > 0; }
|
bool HasTriggers() const noexcept { return before_commit_triggers_.size() > 0 || after_commit_triggers_.size() > 0; }
|
||||||
|
std::unordered_set<TriggerEventType> GetEventTypes() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
utils::SpinLock store_lock_;
|
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 "
|
fmt::format("CREATE TRIGGER SetVertexPropertiesTrigger ON () UPDATE "
|
||||||
"{} COMMIT "
|
"{} COMMIT "
|
||||||
"EXECUTE "
|
"EXECUTE "
|
||||||
"UNWIND assignedVertexProperties as assignedVertexProperty "
|
"UNWIND setVertexProperties as assignedVertexProperty "
|
||||||
"CREATE (n: {} {{ id: assignedVertexProperty.vertex.id }})",
|
"CREATE (n: {} {{ id: assignedVertexProperty.vertex.id }})",
|
||||||
before_or_after, kTriggerSetVertexPropertyLabel));
|
before_or_after, kTriggerSetVertexPropertyLabel));
|
||||||
client.DiscardAll();
|
client.DiscardAll();
|
||||||
@ -121,7 +121,7 @@ void CreateOnUpdateTriggers(mg::Client &client, bool is_before) {
|
|||||||
fmt::format("CREATE TRIGGER SetVertexLabelsTrigger ON () UPDATE "
|
fmt::format("CREATE TRIGGER SetVertexLabelsTrigger ON () UPDATE "
|
||||||
"{} COMMIT "
|
"{} COMMIT "
|
||||||
"EXECUTE "
|
"EXECUTE "
|
||||||
"UNWIND assignedVertexLabels as assignedVertexLabel "
|
"UNWIND setVertexLabels as assignedVertexLabel "
|
||||||
"UNWIND assignedVertexLabel.vertices as vertex "
|
"UNWIND assignedVertexLabel.vertices as vertex "
|
||||||
"CREATE (n: {} {{ id: vertex.id }})",
|
"CREATE (n: {} {{ id: vertex.id }})",
|
||||||
before_or_after, kTriggerSetVertexLabelLabel));
|
before_or_after, kTriggerSetVertexLabelLabel));
|
||||||
@ -140,7 +140,7 @@ void CreateOnUpdateTriggers(mg::Client &client, bool is_before) {
|
|||||||
fmt::format("CREATE TRIGGER SetEdgePropertiesTrigger ON --> UPDATE "
|
fmt::format("CREATE TRIGGER SetEdgePropertiesTrigger ON --> UPDATE "
|
||||||
"{} COMMIT "
|
"{} COMMIT "
|
||||||
"EXECUTE "
|
"EXECUTE "
|
||||||
"UNWIND assignedEdgeProperties as assignedEdgeProperty "
|
"UNWIND setEdgeProperties as assignedEdgeProperty "
|
||||||
"CREATE (n: {} {{ id: assignedEdgeProperty.edge.id }})",
|
"CREATE (n: {} {{ id: assignedEdgeProperty.edge.id }})",
|
||||||
before_or_after, kTriggerSetEdgePropertyLabel));
|
before_or_after, kTriggerSetEdgePropertyLabel));
|
||||||
client.DiscardAll();
|
client.DiscardAll();
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
#include "query/db_accessor.hpp"
|
#include "query/db_accessor.hpp"
|
||||||
#include "query/interpreter.hpp"
|
#include "query/interpreter.hpp"
|
||||||
#include "query/trigger.hpp"
|
#include "query/trigger.hpp"
|
||||||
#include "query/typed_value.hpp"
|
#include "query/typed_value.hpp"
|
||||||
#include "utils/memory.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 {
|
class TriggerContextTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
void SetUp() override { db.emplace(); }
|
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) {
|
const size_t expected_size, query::DbAccessor &dba) {
|
||||||
auto typed_values = trigger_context.GetTypedValue(tag, &dba);
|
auto typed_values = trigger_context.GetTypedValue(tag, &dba);
|
||||||
ASSERT_TRUE(typed_values.IsList());
|
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,
|
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)
|
// that exist (unless its explicitly created for the deleted object)
|
||||||
TEST_F(TriggerContextTest, ValidObjectsTest) {
|
TEST_F(TriggerContextTest, ValidObjectsTest) {
|
||||||
query::TriggerContext trigger_context;
|
query::TriggerContext trigger_context;
|
||||||
query::TriggerContextCollector trigger_context_collector;
|
query::TriggerContextCollector trigger_context_collector{kAllEventTypes};
|
||||||
|
|
||||||
size_t vertex_count = 0;
|
size_t vertex_count = 0;
|
||||||
size_t edge_count = 0;
|
size_t edge_count = 0;
|
||||||
@ -95,7 +105,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) {
|
|||||||
|
|
||||||
dba.AdvanceCommand();
|
dba.AdvanceCommand();
|
||||||
trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
|
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
|
// Should have all the created objects
|
||||||
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_VERTICES, vertex_count, dba);
|
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::CREATED_VERTICES, vertex_count, dba);
|
||||||
@ -181,7 +191,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) {
|
|||||||
ASSERT_FALSE(dba.Commit().HasError());
|
ASSERT_FALSE(dba.Commit().HasError());
|
||||||
|
|
||||||
trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
|
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_VERTEX_PROPERTIES, vertex_count, dba);
|
||||||
CheckTypedValueSize(trigger_context, query::TriggerIdentifierTag::SET_EDGE_PROPERTIES, edge_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
|
// Binding the trigger context to transaction will mean that creating and updating an object in the same transaction
|
||||||
// will return only the CREATE event.
|
// will return only the CREATE event.
|
||||||
TEST_F(TriggerContextTest, ReturnCreateOnlyEvent) {
|
TEST_F(TriggerContextTest, ReturnCreateOnlyEvent) {
|
||||||
query::TriggerContextCollector trigger_context_collector;
|
query::TriggerContextCollector trigger_context_collector{kAllEventTypes};
|
||||||
|
|
||||||
query::DbAccessor dba{&StartTransaction()};
|
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.
|
// transaction) everything inbetween should be ignored.
|
||||||
TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
||||||
query::DbAccessor dba{&StartTransaction()};
|
query::DbAccessor dba{&StartTransaction()};
|
||||||
|
const std::unordered_set<query::TriggerEventType> event_types{query::TriggerEventType::VERTEX_UPDATE};
|
||||||
|
|
||||||
auto v = dba.InsertVertex();
|
auto v = dba.InsertVertex();
|
||||||
dba.AdvanceCommand();
|
dba.AdvanceCommand();
|
||||||
|
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("SET -> SET");
|
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"),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
|
||||||
query::TypedValue("ValueNew"));
|
query::TypedValue("ValueNew"));
|
||||||
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
@ -339,7 +350,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("SET -> REMOVE");
|
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"),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
|
||||||
query::TypedValue("ValueNew"));
|
query::TypedValue("ValueNew"));
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
@ -360,7 +371,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("REMOVE -> SET");
|
SPDLOG_DEBUG("REMOVE -> SET");
|
||||||
query::TriggerContextCollector trigger_context_collector;
|
query::TriggerContextCollector trigger_context_collector{event_types};
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
query::TypedValue("Value"));
|
query::TypedValue("Value"));
|
||||||
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
|
||||||
@ -382,7 +393,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("REMOVE -> REMOVE");
|
SPDLOG_DEBUG("REMOVE -> REMOVE");
|
||||||
query::TriggerContextCollector trigger_context_collector;
|
query::TriggerContextCollector trigger_context_collector{event_types};
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
query::TypedValue("Value"));
|
query::TypedValue("Value"));
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue());
|
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)");
|
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"),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value"),
|
||||||
query::TypedValue("ValueNew"));
|
query::TypedValue("ValueNew"));
|
||||||
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
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)");
|
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(),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
|
||||||
query::TypedValue("ValueNew"));
|
query::TypedValue("ValueNew"));
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
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)");
|
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"),
|
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
query::TypedValue("Value"));
|
query::TypedValue("Value"));
|
||||||
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue(),
|
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)");
|
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());
|
||||||
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();
|
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");
|
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"),
|
trigger_context_collector.RegisterSetObjectProperty(v, dba.NameToProperty("PROPERTY"), query::TypedValue("Value0"),
|
||||||
query::TypedValue("Value1"));
|
query::TypedValue("Value1"));
|
||||||
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
trigger_context_collector.RegisterRemovedObjectProperty(v, dba.NameToProperty("PROPERTY"),
|
||||||
@ -486,6 +497,7 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) {
|
|||||||
// Same as above, but for label changes
|
// Same as above, but for label changes
|
||||||
TEST_F(TriggerContextTest, GlobalLabelChange) {
|
TEST_F(TriggerContextTest, GlobalLabelChange) {
|
||||||
query::DbAccessor dba{&StartTransaction()};
|
query::DbAccessor dba{&StartTransaction()};
|
||||||
|
const std::unordered_set<query::TriggerEventType> event_types{query::TriggerEventType::VERTEX_UPDATE};
|
||||||
|
|
||||||
auto v = dba.InsertVertex();
|
auto v = dba.InsertVertex();
|
||||||
dba.AdvanceCommand();
|
dba.AdvanceCommand();
|
||||||
@ -495,7 +507,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
|
|||||||
// so REMOVE -> REMOVE and SET -> SET doesn't make sense
|
// so REMOVE -> REMOVE and SET -> SET doesn't make sense
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("SET -> REMOVE");
|
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.RegisterSetVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
|
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
|
||||||
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
|
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
|
||||||
@ -507,7 +519,7 @@ TEST_F(TriggerContextTest, GlobalLabelChange) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
SPDLOG_DEBUG("REMOVE -> SET");
|
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.RegisterRemovedVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
|
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
|
||||||
const auto trigger_context = std::move(trigger_context_collector).TransformToTriggerContext();
|
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");
|
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.RegisterSetVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
|
trigger_context_collector.RegisterRemovedVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterSetVertexLabel(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");
|
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.RegisterRemovedVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
|
trigger_context_collector.RegisterSetVertexLabel(v, label_id);
|
||||||
trigger_context_collector.RegisterRemovedVertexLabel(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 {
|
class TriggerStoreTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
const std::filesystem::path testing_directory{std::filesystem::temp_directory_path() / "MG_test_unit_query_trigger"};
|
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();
|
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