From 4bb9238679e2275eecf544149418e64c6bc6f2f9 Mon Sep 17 00:00:00 2001
From: DavIvek <david.ivekovic@memgraph.io>
Date: Tue, 9 Jan 2024 13:05:54 +0100
Subject: [PATCH] Add support for triggers in database dump (#1610)

---
 src/query/dump.cpp        | 40 ++++++++++++++++++++++---
 src/query/dump.hpp        | 11 +++----
 src/query/interpreter.cpp |  2 +-
 tests/unit/query_dump.cpp | 63 +++++++++++++++++++++++++++++----------
 4 files changed, 90 insertions(+), 26 deletions(-)

diff --git a/src/query/dump.cpp b/src/query/dump.cpp
index a1421cbf9..2925023fb 100644
--- a/src/query/dump.cpp
+++ b/src/query/dump.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,6 +11,7 @@
 
 #include "query/dump.hpp"
 
+#include <algorithm>
 #include <iomanip>
 #include <limits>
 #include <map>
@@ -21,9 +22,11 @@
 
 #include <fmt/format.h>
 
+#include "dbms/database.hpp"
 #include "query/db_accessor.hpp"
 #include "query/exceptions.hpp"
 #include "query/stream.hpp"
+#include "query/trigger_context.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/property_value.hpp"
 #include "storage/v2/storage.hpp"
@@ -260,10 +263,20 @@ void DumpUniqueConstraint(std::ostream *os, query::DbAccessor *dba, storage::Lab
   *os << " IS UNIQUE;";
 }
 
+const char *triggerPhaseToString(TriggerPhase phase) {
+  switch (phase) {
+    case TriggerPhase::BEFORE_COMMIT:
+      return "BEFORE COMMIT EXECUTE";
+    case TriggerPhase::AFTER_COMMIT:
+      return "AFTER COMMIT EXECUTE";
+  }
+}
+
 }  // namespace
 
-PullPlanDump::PullPlanDump(DbAccessor *dba)
+PullPlanDump::PullPlanDump(DbAccessor *dba, dbms::DatabaseAccess db_acc)
     : dba_(dba),
+      db_acc_(db_acc),
       vertices_iterable_(dba->Vertices(storage::View::OLD)),
       pull_chunks_{// Dump all label indices
                    CreateLabelIndicesPullChunk(),
@@ -282,7 +295,9 @@ PullPlanDump::PullPlanDump(DbAccessor *dba)
                    // Drop the internal index
                    CreateDropInternalIndexPullChunk(),
                    // Internal index cleanup
-                   CreateInternalIndexCleanupPullChunk()} {}
+                   CreateInternalIndexCleanupPullChunk(),
+                   // Dump all triggers
+                   CreateTriggersPullChunk()} {}
 
 bool PullPlanDump::Pull(AnyStream *stream, std::optional<int> n) {
   // Iterate all functions that stream some results.
@@ -536,6 +551,23 @@ PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexCleanupPullChunk() {
   };
 }
 
-void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream) { PullPlanDump(dba).Pull(stream, {}); }
+PullPlanDump::PullChunk PullPlanDump::CreateTriggersPullChunk() {
+  return [this](AnyStream *stream, std::optional<int>) {
+    auto triggers = db_acc_->trigger_store()->GetTriggerInfo();
+    for (const auto &trigger : triggers) {
+      std::ostringstream os;
+      auto trigger_statement_copy = trigger.statement;
+      std::replace(trigger_statement_copy.begin(), trigger_statement_copy.end(), '\n', ' ');
+      os << "CREATE TRIGGER " << trigger.name << " ON " << memgraph::query::TriggerEventTypeToString(trigger.event_type)
+         << " " << triggerPhaseToString(trigger.phase) << " " << trigger_statement_copy << ";";
+      stream->Result({TypedValue(os.str())});
+    }
+    return 0;
+  };
+}
+
+void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc) {
+  PullPlanDump(dba, db_acc).Pull(stream, {});
+}
 
 }  // namespace memgraph::query
diff --git a/src/query/dump.hpp b/src/query/dump.hpp
index b15a8d9e9..a9d68d45c 100644
--- a/src/query/dump.hpp
+++ b/src/query/dump.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,18 +11,17 @@
 
 #pragma once
 
-#include <ostream>
-
+#include "dbms/database.hpp"
 #include "query/db_accessor.hpp"
 #include "query/stream.hpp"
 #include "storage/v2/storage.hpp"
 
 namespace memgraph::query {
 
-void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream);
+void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc);
 
 struct PullPlanDump {
-  explicit PullPlanDump(query::DbAccessor *dba);
+  explicit PullPlanDump(query::DbAccessor *dba, dbms::DatabaseAccess db_acc);
 
   /// Pull the dump results lazily
   /// @return true if all results were returned, false otherwise
@@ -30,6 +29,7 @@ struct PullPlanDump {
 
  private:
   query::DbAccessor *dba_ = nullptr;
+  dbms::DatabaseAccess db_acc_;
 
   std::optional<storage::IndicesInfo> indices_info_ = std::nullopt;
   std::optional<storage::ConstraintsInfo> constraints_info_ = std::nullopt;
@@ -62,5 +62,6 @@ struct PullPlanDump {
   PullChunk CreateEdgePullChunk();
   PullChunk CreateDropInternalIndexPullChunk();
   PullChunk CreateInternalIndexCleanupPullChunk();
+  PullChunk CreateTriggersPullChunk();
 };
 }  // namespace memgraph::query
diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp
index 9731c3484..c629683f3 100644
--- a/src/query/interpreter.cpp
+++ b/src/query/interpreter.cpp
@@ -1818,7 +1818,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
 PreparedQuery PrepareDumpQuery(ParsedQuery parsed_query, CurrentDB &current_db) {
   MG_ASSERT(current_db.execution_db_accessor_, "Dump query expects a current DB transaction");
   auto *dba = &*current_db.execution_db_accessor_;
-  auto plan = std::make_shared<PullPlanDump>(dba);
+  auto plan = std::make_shared<PullPlanDump>(dba, *current_db.db_acc_);
   return PreparedQuery{
       {"QUERY"},
       std::move(parsed_query.required_privileges),
diff --git a/tests/unit/query_dump.cpp b/tests/unit/query_dump.cpp
index 1a4dd6264..a1165789b 100644
--- a/tests/unit/query_dump.cpp
+++ b/tests/unit/query_dump.cpp
@@ -9,10 +9,12 @@
 // by the Apache License, Version 2.0, included in the file
 // licenses/APL.txt.
 
+#include <gtest/gtest-typed-test.h>
 #include <gtest/gtest.h>
 
 #include <filesystem>
 #include <map>
+#include <optional>
 #include <set>
 #include <vector>
 
@@ -24,6 +26,8 @@
 #include "query/interpreter.hpp"
 #include "query/interpreter_context.hpp"
 #include "query/stream/streams.hpp"
+#include "query/trigger.hpp"
+#include "query/trigger_context.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/config.hpp"
 #include "storage/v2/disk/storage.hpp"
@@ -331,7 +335,7 @@ TYPED_TEST(DumpTest, EmptyGraph) {
   {
     auto acc = this->db->Access();
     memgraph::query::DbAccessor dba(acc.get());
-    memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+    memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
   }
   ASSERT_EQ(stream.GetResults().size(), 0);
 }
@@ -350,7 +354,7 @@ TYPED_TEST(DumpTest, SingleVertex) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -371,7 +375,7 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1` {__mg_id__: 0});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -392,7 +396,7 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex,
                   "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0});", kDropInternalIndex,
@@ -414,7 +418,7 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, `prop`: 42});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -437,7 +441,7 @@ TYPED_TEST(DumpTest, MultipleVertices) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});",
@@ -475,7 +479,7 @@ TYPED_TEST(DumpTest, PropertyValue) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex,
                   "CREATE (:__mg_vertex__ {__mg_id__: 0, `p1`: [{`prop 1`: 13, "
@@ -502,7 +506,7 @@ TYPED_TEST(DumpTest, SingleEdge) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});",
@@ -531,7 +535,7 @@ TYPED_TEST(DumpTest, MultipleEdges) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});",
@@ -561,7 +565,7 @@ TYPED_TEST(DumpTest, EdgeWithProperties) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});",
@@ -601,7 +605,7 @@ TYPED_TEST(DumpTest, IndicesKeys) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), "CREATE INDEX ON :`Label1`(`prop`);", "CREATE INDEX ON :`Label 2`(`prop ```);",
                   kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0, `p`: 1});",
@@ -630,7 +634,7 @@ TYPED_TEST(DumpTest, ExistenceConstraints) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:`L``abel 1`) ASSERT EXISTS (u.`prop`);",
                   kCreateInternalIndex, "CREATE (:__mg_vertex__:`L``abel 1` {__mg_id__: 0, `prop`: 1});",
@@ -665,7 +669,7 @@ TYPED_TEST(DumpTest, UniqueConstraints) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(),
                   "CREATE CONSTRAINT ON (u:`Label`) ASSERT u.`prop`, u.`prop2` "
@@ -724,7 +728,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     const auto &results = stream.GetResults();
     ASSERT_GE(results.size(), 1);
@@ -841,7 +845,7 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
     {
       auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     const auto &results = stream.GetResults();
     // Indices and constraints are 4 queries and there must be at least one more
@@ -1056,7 +1060,7 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
   auto acc = this->db->Access();
   memgraph::query::DbAccessor dba(acc.get());
 
-  memgraph::query::PullPlanDump pullPlan{&dba};
+  memgraph::query::PullPlanDump pullPlan{&dba, this->db};
 
   auto offset_index = 0U;
   auto check_next = [&](const std::string &expected_row) mutable {
@@ -1095,3 +1099,30 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
   check_next(kDropInternalIndex);
   check_next(kRemoveInternalLabelProperty);
 }
+
+TYPED_TEST(DumpTest, DumpDatabaseWithTriggers) {
+  auto acc = this->db->storage()->Access(memgraph::replication::ReplicationRole::MAIN);
+  memgraph::query::DbAccessor dba(acc.get());
+  {
+    auto trigger_store = this->db.get()->trigger_store();
+    const std::string trigger_name = "test_trigger";
+    const std::string trigger_statement = "UNWIND createdVertices AS newNodes SET newNodes.created = timestamp()";
+    memgraph::query::TriggerEventType trigger_event_type = memgraph::query::TriggerEventType::VERTEX_CREATE;
+    memgraph::query::TriggerPhase trigger_phase = memgraph::query::TriggerPhase::AFTER_COMMIT;
+    memgraph::utils::SkipList<memgraph::query::QueryCacheEntry> ast_cache;
+    memgraph::query::AllowEverythingAuthChecker auth_checker;
+    memgraph::query::InterpreterConfig::Query query_config;
+    memgraph::query::DbAccessor dba(acc.get());
+    const std::map<std::string, memgraph::storage::PropertyValue> props;
+    trigger_store->AddTrigger(trigger_name, trigger_statement, props, trigger_event_type, trigger_phase, &ast_cache,
+                              &dba, query_config, std::nullopt, &auth_checker);
+  }
+  {
+    ResultStreamFaker stream(this->db->storage());
+    memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
+    { memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); }
+    VerifyQueries(stream.GetResults(),
+                  "CREATE TRIGGER test_trigger ON () CREATE AFTER COMMIT EXECUTE UNWIND createdVertices AS newNodes "
+                  "SET newNodes.created = timestamp();");
+  }
+}