From 281cebd38681d791f2207d0b48216e3e482ad194 Mon Sep 17 00:00:00 2001
From: gvolfing <107616712+gvolfing@users.noreply.github.com>
Date: Thu, 20 Oct 2022 17:51:44 +0200
Subject: [PATCH] Add filtering capabilities to ExpandOne request (#581)

---
 src/query/v2/requests.hpp      |   9 +-
 src/storage/v3/shard_rsm.cpp   | 200 ++++++++++++++++++++++++---------
 src/storage/v3/vertex_id.hpp   |   7 ++
 tests/simulation/shard_rsm.cpp | 180 ++++++++++++++++++++++++++---
 4 files changed, 319 insertions(+), 77 deletions(-)

diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp
index d8a8ed287..12fa2ea95 100644
--- a/src/query/v2/requests.hpp
+++ b/src/query/v2/requests.hpp
@@ -399,13 +399,14 @@ struct ExpandOneRequest {
   std::optional<std::vector<PropertyId>> src_vertex_properties;
   //  The empty optional means return all of the properties, while an empty list means do not return any properties
   std::optional<std::vector<PropertyId>> edge_properties;
-  //  QUESTION(antaljanosbenjamin): Maybe also add possibility to expressions evaluated on the source vertex?
-  //  List of expressions evaluated on edges
-  std::vector<Expression> expressions;
+
+  std::vector<std::string> vertex_expressions;
+  std::vector<std::string> edge_expressions;
+
   std::optional<std::vector<OrderBy>> order_by;
   // Limit the edges or the vertices?
   std::optional<size_t> limit;
-  std::optional<Filter> filter;
+  std::vector<std::string> filters;
 };
 
 struct ExpandOneResultRow {
diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp
index 1e5609b23..f598c6c54 100644
--- a/src/storage/v3/shard_rsm.cpp
+++ b/src/storage/v3/shard_rsm.cpp
@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <functional>
 #include <iterator>
+#include <unordered_set>
 #include <utility>
 
 #include "parser/opencypher/parser.hpp"
@@ -50,6 +51,24 @@ using conversions::ToPropertyValue;
 namespace {
 namespace msgs = msgs;
 
+using AllEdgePropertyDataSructure = std::map<PropertyId, msgs::Value>;
+using SpecificEdgePropertyDataSructure = std::vector<msgs::Value>;
+
+using AllEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, AllEdgePropertyDataSructure>;
+using SpecificEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, SpecificEdgePropertyDataSructure>;
+
+using SpecificEdgePropertiesVector = std::vector<SpecificEdgeProperties>;
+using AllEdgePropertiesVector = std::vector<AllEdgeProperties>;
+
+using EdgeAccessors = std::vector<memgraph::storage::v3::EdgeAccessor>;
+
+using EdgeFiller = std::function<bool(const EdgeAccessor &edge, bool is_in_edge, msgs::ExpandOneResultRow &result_row)>;
+using EdgeUniqunessFunction = std::function<EdgeAccessors(EdgeAccessors &&, memgraph::msgs::EdgeDirection)>;
+
+struct VertexIdCmpr {
+  bool operator()(const storage::v3::VertexId *lhs, const storage::v3::VertexId *rhs) const { return *lhs < *rhs; }
+};
+
 std::vector<std::pair<PropertyId, PropertyValue>> ConvertPropertyMap(
     std::vector<std::pair<PropertyId, Value>> &&properties) {
   std::vector<std::pair<PropertyId, PropertyValue>> ret;
@@ -392,7 +411,8 @@ std::optional<std::map<PropertyId, Value>> FillUpSourceVertexProperties(const st
 }
 
 std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
-    const std::optional<VertexAccessor> &v_acc, const msgs::ExpandOneRequest &req) {
+    const std::optional<VertexAccessor> &v_acc, const msgs::ExpandOneRequest &req,
+    const EdgeUniqunessFunction &maybe_filter_based_on_edge_uniquness) {
   std::vector<EdgeAccessor> in_edges;
   std::vector<EdgeAccessor> out_edges;
 
@@ -404,7 +424,8 @@ std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
                       req.transaction_id.logical_id);
         return std::nullopt;
       }
-      out_edges = std::move(out_edges_result.GetValue());
+      out_edges =
+          maybe_filter_based_on_edge_uniquness(std::move(out_edges_result.GetValue()), msgs::EdgeDirection::OUT);
       break;
     }
     case msgs::EdgeDirection::IN: {
@@ -415,7 +436,7 @@ std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
                                                                                                       .logical_id]);
         return std::nullopt;
       }
-      in_edges = std::move(in_edges_result.GetValue());
+      in_edges = maybe_filter_based_on_edge_uniquness(std::move(in_edges_result.GetValue()), msgs::EdgeDirection::IN);
       break;
     }
     case msgs::EdgeDirection::BOTH: {
@@ -425,7 +446,7 @@ std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
                       req.transaction_id.logical_id);
         return std::nullopt;
       }
-      in_edges = std::move(in_edges_result.GetValue());
+      in_edges = maybe_filter_based_on_edge_uniquness(std::move(in_edges_result.GetValue()), msgs::EdgeDirection::IN);
 
       auto out_edges_result = v_acc->OutEdges(View::NEW);
       if (out_edges_result.HasError()) {
@@ -433,24 +454,14 @@ std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
                       req.transaction_id.logical_id);
         return std::nullopt;
       }
-      out_edges = std::move(out_edges_result.GetValue());
+      out_edges =
+          maybe_filter_based_on_edge_uniquness(std::move(out_edges_result.GetValue()), msgs::EdgeDirection::OUT);
       break;
     }
   }
   return std::array<std::vector<EdgeAccessor>, 2>{in_edges, out_edges};
 }
 
-using AllEdgePropertyDataSructure = std::map<PropertyId, msgs::Value>;
-using SpecificEdgePropertyDataSructure = std::vector<msgs::Value>;
-
-using AllEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, AllEdgePropertyDataSructure>;
-using SpecificEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, SpecificEdgePropertyDataSructure>;
-
-using SpecificEdgePropertiesVector = std::vector<SpecificEdgeProperties>;
-using AllEdgePropertiesVector = std::vector<AllEdgeProperties>;
-
-using EdgeFiller = std::function<bool(const EdgeAccessor &edge, bool is_in_edge, msgs::ExpandOneResultRow &result_row)>;
-
 template <bool are_in_edges>
 bool FillEdges(const std::vector<EdgeAccessor> &edges, const msgs::ExpandOneRequest &req, msgs::ExpandOneResultRow &row,
                const EdgeFiller &edge_filler) {
@@ -467,8 +478,101 @@ bool FillEdges(const std::vector<EdgeAccessor> &edges, const msgs::ExpandOneRequ
   return true;
 }
 
-std::optional<msgs::ExpandOneResultRow> GetExpandOneResult(Shard::Accessor &acc, msgs::VertexId src_vertex,
-                                                           const msgs::ExpandOneRequest &req) {
+std::optional<msgs::ExpandOneResultRow> GetExpandOneResult(
+    Shard::Accessor &acc, msgs::VertexId src_vertex, const msgs::ExpandOneRequest &req,
+    const EdgeUniqunessFunction &maybe_filter_based_on_edge_uniquness, const EdgeFiller &edge_filler) {
+  /// Fill up source vertex
+  const auto primary_key = ConvertPropertyVector(std::move(src_vertex.second));
+  auto v_acc = acc.FindVertex(primary_key, View::NEW);
+
+  auto source_vertex = FillUpSourceVertex(v_acc, req, src_vertex);
+  if (!source_vertex) {
+    return std::nullopt;
+  }
+
+  /// Fill up source vertex properties
+  auto src_vertex_properties = FillUpSourceVertexProperties(v_acc, req);
+  if (!src_vertex_properties) {
+    return std::nullopt;
+  }
+
+  /// Fill up connecting edges
+  auto fill_up_connecting_edges = FillUpConnectingEdges(v_acc, req, maybe_filter_based_on_edge_uniquness);
+  if (!fill_up_connecting_edges) {
+    return std::nullopt;
+  }
+
+  auto [in_edges, out_edges] = fill_up_connecting_edges.value();
+
+  msgs::ExpandOneResultRow result_row;
+  result_row.src_vertex = std::move(*source_vertex);
+  result_row.src_vertex_properties = std::move(*src_vertex_properties);
+  static constexpr bool kInEdges = true;
+  static constexpr bool kOutEdges = false;
+  if (!in_edges.empty() && !FillEdges<kInEdges>(in_edges, req, result_row, edge_filler)) {
+    return std::nullopt;
+  }
+  if (!out_edges.empty() && !FillEdges<kOutEdges>(out_edges, req, result_row, edge_filler)) {
+    return std::nullopt;
+  }
+
+  return result_row;
+}
+
+EdgeUniqunessFunction InitializeEdgeUniqunessFunction(bool only_unique_neighbor_rows) {
+  // Functions to select connecting edges based on uniquness
+  EdgeUniqunessFunction maybe_filter_based_on_edge_uniquness;
+
+  if (only_unique_neighbor_rows) {
+    maybe_filter_based_on_edge_uniquness = [](EdgeAccessors &&edges,
+                                              memgraph::msgs::EdgeDirection edge_direction) -> EdgeAccessors {
+      std::function<bool(std::set<const storage::v3::VertexId *, VertexIdCmpr> &,
+                         const memgraph::storage::v3::EdgeAccessor &)>
+          is_edge_unique;
+      switch (edge_direction) {
+        case memgraph::msgs::EdgeDirection::OUT: {
+          is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
+                              const memgraph::storage::v3::EdgeAccessor &edge_acc) {
+            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.ToVertex());
+            return insertion_happened;
+          };
+          break;
+        }
+        case memgraph::msgs::EdgeDirection::IN: {
+          is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
+                              const memgraph::storage::v3::EdgeAccessor &edge_acc) {
+            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.FromVertex());
+            return insertion_happened;
+          };
+          break;
+        }
+        case memgraph::msgs::EdgeDirection::BOTH:
+          MG_ASSERT(false,
+                    "This is should never happen, memgraph::msgs::EdgeDirection::BOTH should not be passed here.");
+      }
+
+      EdgeAccessors ret;
+      std::set<const storage::v3::VertexId *, VertexIdCmpr> other_vertex_set;
+
+      for (const auto &edge : edges) {
+        if (is_edge_unique(other_vertex_set, edge)) {
+          ret.emplace_back(edge);
+        }
+      }
+
+      return ret;
+    };
+  } else {
+    maybe_filter_based_on_edge_uniquness = [](EdgeAccessors &&edges,
+                                              memgraph::msgs::EdgeDirection /*edge_direction*/) -> EdgeAccessors {
+      return std::move(edges);
+    };
+  }
+
+  return maybe_filter_based_on_edge_uniquness;
+}
+
+EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) {
   EdgeFiller edge_filler;
 
   if (!req.edge_properties) {
@@ -521,43 +625,9 @@ std::optional<msgs::ExpandOneResultRow> GetExpandOneResult(Shard::Accessor &acc,
     };
   }
 
-  /// Fill up source vertex
-  const auto primary_key = ConvertPropertyVector(std::move(src_vertex.second));
-  auto v_acc = acc.FindVertex(primary_key, View::NEW);
-
-  auto source_vertex = FillUpSourceVertex(v_acc, req, src_vertex);
-  if (!source_vertex) {
-    return std::nullopt;
-  }
-
-  /// Fill up source vertex properties
-  auto src_vertex_properties = FillUpSourceVertexProperties(v_acc, req);
-  if (!src_vertex_properties) {
-    return std::nullopt;
-  }
-
-  /// Fill up connecting edges
-  auto fill_up_connecting_edges = FillUpConnectingEdges(v_acc, req);
-  if (!fill_up_connecting_edges) {
-    return std::nullopt;
-  }
-
-  auto [in_edges, out_edges] = fill_up_connecting_edges.value();
-
-  msgs::ExpandOneResultRow result_row;
-  result_row.src_vertex = std::move(*source_vertex);
-  result_row.src_vertex_properties = std::move(*src_vertex_properties);
-  static constexpr bool kInEdges = true;
-  static constexpr bool kOutEdges = false;
-  if (!in_edges.empty() && !FillEdges<kInEdges>(in_edges, req, result_row, edge_filler)) {
-    return std::nullopt;
-  }
-  if (!out_edges.empty() && !FillEdges<kOutEdges>(out_edges, req, result_row, edge_filler)) {
-    return std::nullopt;
-  }
-
-  return result_row;
+  return edge_filler;
 }
+
 };  // namespace
 msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
   auto acc = shard_->Access(req.transaction_id);
@@ -939,8 +1009,28 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest &&req) {
 
   std::vector<msgs::ExpandOneResultRow> results;
 
+  auto maybe_filter_based_on_edge_uniquness = InitializeEdgeUniqunessFunction(req.only_unique_neighbor_rows);
+  auto edge_filler = InitializeEdgeFillerFunction(req);
+
   for (auto &src_vertex : req.src_vertices) {
-    auto result = GetExpandOneResult(acc, src_vertex, req);
+    // Get Vertex acc
+    auto src_vertex_acc_opt = acc.FindVertex(ConvertPropertyVector((src_vertex.second)), View::NEW);
+    if (!src_vertex_acc_opt) {
+      action_successful = false;
+      spdlog::debug("Encountered an error while trying to obtain VertexAccessor. Transaction id: {}",
+                    req.transaction_id.logical_id);
+      break;
+    }
+
+    if (!req.filters.empty()) {
+      // NOTE - DbAccessor might get removed in the future.
+      auto dba = DbAccessor{&acc};
+      const bool eval = FilterOnVertex(dba, src_vertex_acc_opt.value(), req.filters, expr::identifier_node_symbol);
+      if (!eval) {
+        continue;
+      }
+    }
+    auto result = GetExpandOneResult(acc, src_vertex, req, maybe_filter_based_on_edge_uniquness, edge_filler);
 
     if (!result) {
       action_successful = false;
diff --git a/src/storage/v3/vertex_id.hpp b/src/storage/v3/vertex_id.hpp
index 70dc85ec8..7c136f662 100644
--- a/src/storage/v3/vertex_id.hpp
+++ b/src/storage/v3/vertex_id.hpp
@@ -11,6 +11,8 @@
 
 #pragma once
 
+#include <tuple>
+
 #include "storage/v3/id_types.hpp"
 #include "storage/v3/key_store.hpp"
 
@@ -29,4 +31,9 @@ struct VertexId {
 inline bool operator==(const VertexId &lhs, const VertexId &rhs) {
   return lhs.primary_label == rhs.primary_label && lhs.primary_key == rhs.primary_key;
 }
+
+inline bool operator<(const VertexId &lhs, const VertexId &rhs) {
+  return std::tie(lhs.primary_label, lhs.primary_key) < std::tie(rhs.primary_label, rhs.primary_key);
+}
+
 }  // namespace memgraph::storage::v3
diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp
index 953e0579a..aec26f6c1 100644
--- a/tests/simulation/shard_rsm.cpp
+++ b/tests/simulation/shard_rsm.cpp
@@ -463,18 +463,18 @@ void AttemptToExpandOneWithWrongEdgeType(ShardClient &client, uint64_t src_verte
   // Edge properties to look for
   std::optional<std::vector<PropertyId>> edge_properties = {};
 
-  std::vector<msgs::Expression> expressions;
+  std::vector<std::string> expressions;
   std::optional<std::vector<msgs::OrderBy>> order_by = {};
   std::optional<size_t> limit = {};
-  std::optional<msgs::Filter> filter = {};
+  std::vector<std::string> filter = {};
 
   msgs::ExpandOneRequest expand_one_req{};
 
   expand_one_req.direction = edge_direction;
   expand_one_req.edge_properties = edge_properties;
   expand_one_req.edge_types = {edge_type};
-  expand_one_req.expressions = expressions;
-  expand_one_req.filter = filter;
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = filter;
   expand_one_req.limit = limit;
   expand_one_req.order_by = order_by;
   expand_one_req.src_vertex_properties = src_vertex_properties;
@@ -518,18 +518,18 @@ void AttemptToExpandOneSimple(ShardClient &client, uint64_t src_vertex_val, Edge
   // Edge properties to look for
   std::optional<std::vector<PropertyId>> edge_properties = {};
 
-  std::vector<msgs::Expression> expressions;
+  std::vector<std::string> expressions;
   std::optional<std::vector<msgs::OrderBy>> order_by = {};
   std::optional<size_t> limit = {};
-  std::optional<msgs::Filter> filter = {};
+  std::vector<std::string> filter = {};
 
   msgs::ExpandOneRequest expand_one_req{};
 
   expand_one_req.direction = edge_direction;
   expand_one_req.edge_properties = edge_properties;
   expand_one_req.edge_types = {edge_type};
-  expand_one_req.expressions = expressions;
-  expand_one_req.filter = filter;
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = filter;
   expand_one_req.limit = limit;
   expand_one_req.order_by = order_by;
   expand_one_req.src_vertex_properties = src_vertex_properties;
@@ -556,6 +556,63 @@ void AttemptToExpandOneSimple(ShardClient &client, uint64_t src_vertex_val, Edge
   }
 }
 
+void AttemptToExpandOneWithUniqueEdges(ShardClient &client, uint64_t src_vertex_val, EdgeTypeId edge_type_id) {
+  // Source vertex
+  msgs::Label label = {.id = get_primary_label()};
+  auto src_vertex = std::make_pair(label, GetPrimaryKey(src_vertex_val));
+
+  // Edge type
+  auto edge_type = msgs::EdgeType{};
+  edge_type.id = edge_type_id;
+
+  // Edge direction
+  auto edge_direction = msgs::EdgeDirection::OUT;
+
+  // Source Vertex properties to look for
+  std::optional<std::vector<PropertyId>> src_vertex_properties = {};
+
+  // Edge properties to look for
+  std::optional<std::vector<PropertyId>> edge_properties = {};
+
+  std::vector<std::string> expressions;
+  std::optional<std::vector<msgs::OrderBy>> order_by = {};
+  std::optional<size_t> limit = {};
+  std::vector<std::string> filter = {};
+
+  msgs::ExpandOneRequest expand_one_req{};
+
+  expand_one_req.direction = edge_direction;
+  expand_one_req.edge_properties = edge_properties;
+  expand_one_req.edge_types = {edge_type};
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = filter;
+  expand_one_req.limit = limit;
+  expand_one_req.order_by = order_by;
+  expand_one_req.src_vertex_properties = src_vertex_properties;
+  expand_one_req.src_vertices = {src_vertex};
+  expand_one_req.only_unique_neighbor_rows = true;
+  expand_one_req.transaction_id.logical_id = GetTransactionId();
+
+  while (true) {
+    auto read_res = client.SendReadRequest(expand_one_req);
+    if (read_res.HasError()) {
+      continue;
+    }
+
+    auto write_response_result = read_res.GetValue();
+    auto write_response = std::get<msgs::ExpandOneResponse>(write_response_result);
+    MG_ASSERT(write_response.result.size() == 1);
+    MG_ASSERT(write_response.result[0].out_edges_with_all_properties.size() == 1);
+    MG_ASSERT(write_response.result[0].in_edges_with_all_properties.empty());
+    MG_ASSERT(write_response.result[0].in_edges_with_specific_properties.empty());
+    MG_ASSERT(write_response.result[0].out_edges_with_specific_properties.empty());
+    const auto number_of_properties_on_edge =
+        (write_response.result[0].out_edges_with_all_properties[0]).properties.size();
+    MG_ASSERT(number_of_properties_on_edge == 1);
+    break;
+  }
+}
+
 void AttemptToExpandOneWithSpecifiedSrcVertexProperties(ShardClient &client, uint64_t src_vertex_val,
                                                         EdgeTypeId edge_type_id) {
   // Source vertex
@@ -576,18 +633,18 @@ void AttemptToExpandOneWithSpecifiedSrcVertexProperties(ShardClient &client, uin
   // Edge properties to look for
   std::optional<std::vector<PropertyId>> edge_properties = {};
 
-  std::vector<msgs::Expression> expressions;
+  std::vector<std::string> expressions;
   std::optional<std::vector<msgs::OrderBy>> order_by = {};
   std::optional<size_t> limit = {};
-  std::optional<msgs::Filter> filter = {};
+  std::vector<std::string> filter = {};
 
   msgs::ExpandOneRequest expand_one_req{};
 
   expand_one_req.direction = edge_direction;
   expand_one_req.edge_properties = edge_properties;
   expand_one_req.edge_types = {edge_type};
-  expand_one_req.expressions = expressions;
-  expand_one_req.filter = filter;
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = filter;
   expand_one_req.limit = limit;
   expand_one_req.order_by = order_by;
   expand_one_req.src_vertex_properties = src_vertex_properties;
@@ -636,18 +693,18 @@ void AttemptToExpandOneWithSpecifiedEdgeProperties(ShardClient &client, uint64_t
   std::vector<PropertyId> specified_edge_prop{PropertyId::FromUint(edge_prop_id)};
   std::optional<std::vector<PropertyId>> edge_properties = {specified_edge_prop};
 
-  std::vector<msgs::Expression> expressions;
+  std::vector<std::string> expressions;
   std::optional<std::vector<msgs::OrderBy>> order_by = {};
   std::optional<size_t> limit = {};
-  std::optional<msgs::Filter> filter = {};
+  std::vector<std::string> filter = {};
 
   msgs::ExpandOneRequest expand_one_req{};
 
   expand_one_req.direction = edge_direction;
   expand_one_req.edge_properties = edge_properties;
   expand_one_req.edge_types = {edge_type};
-  expand_one_req.expressions = expressions;
-  expand_one_req.filter = filter;
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = filter;
   expand_one_req.limit = limit;
   expand_one_req.order_by = order_by;
   expand_one_req.src_vertex_properties = src_vertex_properties;
@@ -674,6 +731,62 @@ void AttemptToExpandOneWithSpecifiedEdgeProperties(ShardClient &client, uint64_t
   }
 }
 
+void AttemptToExpandOneWithFilters(ShardClient &client, uint64_t src_vertex_val, EdgeTypeId edge_type_id,
+                                   uint64_t edge_prop_id, uint64_t prop_val_to_check_against) {
+  std::string filter_expr1 = "MG_SYMBOL_NODE.property = " + std::to_string(prop_val_to_check_against);
+
+  // Source vertex
+  msgs::Label label = {.id = get_primary_label()};
+  auto src_vertex = std::make_pair(label, GetPrimaryKey(src_vertex_val));
+
+  // Edge type
+  auto edge_type = msgs::EdgeType{};
+  edge_type.id = edge_type_id;
+
+  // Edge direction
+  auto edge_direction = msgs::EdgeDirection::OUT;
+
+  // Source Vertex properties to look for
+  std::optional<std::vector<PropertyId>> src_vertex_properties = {};
+
+  // Edge properties to look for
+  std::optional<std::vector<PropertyId>> edge_properties = {};
+
+  std::vector<std::string> expressions;
+  std::optional<std::vector<msgs::OrderBy>> order_by = {};
+  std::optional<size_t> limit = {};
+  std::vector<std::string> filter = {};
+
+  msgs::ExpandOneRequest expand_one_req{};
+
+  expand_one_req.direction = edge_direction;
+  expand_one_req.edge_properties = edge_properties;
+  expand_one_req.edge_types = {edge_type};
+  expand_one_req.vertex_expressions = expressions;
+  expand_one_req.filters = {filter_expr1};
+  expand_one_req.limit = limit;
+  expand_one_req.order_by = order_by;
+  expand_one_req.src_vertex_properties = src_vertex_properties;
+  expand_one_req.src_vertices = {src_vertex};
+  expand_one_req.transaction_id.logical_id = GetTransactionId();
+
+  while (true) {
+    auto read_res = client.SendReadRequest(expand_one_req);
+    if (read_res.HasError()) {
+      continue;
+    }
+
+    auto write_response_result = read_res.GetValue();
+    auto write_response = std::get<msgs::ExpandOneResponse>(write_response_result);
+    MG_ASSERT(write_response.result.size() == 1);
+    MG_ASSERT(write_response.result[0].out_edges_with_specific_properties.empty());
+    MG_ASSERT(write_response.result[0].in_edges_with_specific_properties.empty());
+    MG_ASSERT(write_response.result[0].in_edges_with_all_properties.empty());
+    MG_ASSERT(write_response.result[0].out_edges_with_all_properties.size() == 2);
+    break;
+  }
+}
+
 }  // namespace
 
 // tests
@@ -818,7 +931,7 @@ void TestScanAllWithSmallBatchSize(ShardClient &client) {
   MG_ASSERT(!next_id4);
 }
 
-void TestExpandOne(ShardClient &client) {
+void TestExpandOneGraphOne(ShardClient &client) {
   {
     // ExpandOneSimple
     auto unique_prop_val_1 = GetUniqueInteger();
@@ -849,6 +962,36 @@ void TestExpandOne(ShardClient &client) {
     AttemptToExpandOneWithWrongEdgeType(client, unique_prop_val_1, wrong_edge_type_id);
     AttemptToExpandOneWithSpecifiedSrcVertexProperties(client, unique_prop_val_1, edge_type_id);
     AttemptToExpandOneWithSpecifiedEdgeProperties(client, unique_prop_val_1, edge_type_id, edge_prop_id);
+    AttemptToExpandOneWithFilters(client, unique_prop_val_1, edge_type_id, edge_prop_id, unique_prop_val_1);
+  }
+}
+
+void TestExpandOneGraphTwo(ShardClient &client) {
+  {
+    // ExpandOneSimple
+    auto unique_prop_val_1 = GetUniqueInteger();
+    auto unique_prop_val_2 = GetUniqueInteger();
+
+    MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_1));
+    MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_2));
+
+    auto edge_type_id = EdgeTypeId::FromUint(GetUniqueInteger());
+    auto wrong_edge_type_id = EdgeTypeId::FromUint(GetUniqueInteger());
+
+    auto edge_gid_1 = GetUniqueInteger();
+    auto edge_gid_2 = GetUniqueInteger();
+
+    auto edge_prop_id = GetUniqueInteger();
+    auto edge_prop_val = GetUniqueInteger();
+
+    // (V1)-[edge_type_id]->(V2)
+    MG_ASSERT(AttemptToAddEdgeWithProperties(client, unique_prop_val_1, unique_prop_val_2, edge_gid_1, edge_prop_id,
+                                             edge_prop_val, {edge_type_id}));
+    // (V1)-[edge_type_id]->(V3)
+    MG_ASSERT(AttemptToAddEdgeWithProperties(client, unique_prop_val_1, unique_prop_val_2, edge_gid_2, edge_prop_id,
+                                             edge_prop_val, {edge_type_id}));
+    // AttemptToExpandOneSimple(client, unique_prop_val_1, edge_type_id);
+    AttemptToExpandOneWithUniqueEdges(client, unique_prop_val_1, edge_type_id);
   }
 }
 
@@ -930,7 +1073,8 @@ int TestMessages() {
   TestScanAllWithSmallBatchSize(client);
 
   // ExpandOne tests
-  TestExpandOne(client);
+  TestExpandOneGraphOne(client);
+  TestExpandOneGraphTwo(client);
 
   simulator.ShutDown();