Repository: alibaba/GraphScope (language: python)

Issue:
Support PathExpand with Multiple Edge Labels on HQPS engine
For example, the following query cannot be executed: 

```
MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..3]-(hollywood)
RETURN DISTINCT bacon, hollywood;
```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/hqps-db-ci.yml b/.github/workflows/hqps-db-ci.yml
index 47a596dfd2c5..e9a36dca3845 100644
--- a/.github/workflows/hqps-db-ci.yml
+++ b/.github/workflows/hqps-db-ci.yml
@@ -122,7 +122,7 @@ jobs:
           eval ${cmd}
         done
 
-        for i in 1 2 3 4 5 6 7 8 9 10;
+        for i in 1 2 3 4 5 6 7 8 9 10 11 12;
         do
           cmd="./load_plan_and_gen.sh -e=hqps -i=../resources/queries/ic/adhoc/simple_match_${i}.cypher -w=/tmp/codgen/"
           cmd=${cmd}" -o=/tmp/plugin --ir_conf=${GS_TEST_DIR}/flex/ldbc-sf01-long-date/engine_config.yaml "
diff --git a/flex/codegen/src/hqps/hqps_edge_expand_builder.h b/flex/codegen/src/hqps/hqps_edge_expand_builder.h
index e11ff3e9ef0a..eb1cedd36899 100644
--- a/flex/codegen/src/hqps/hqps_edge_expand_builder.h
+++ b/flex/codegen/src/hqps/hqps_edge_expand_builder.h
@@ -37,11 +37,16 @@ static constexpr const char* EDGE_EXPAND_V_OPT_FILTER_TEMPLATE_STR =
 static constexpr const char* EDGE_EXPAND_V_OPT_NO_FILTER_TEMPLATE_STR =
     "auto %1% = gs::make_edge_expandv_opt(%2%, %3%, %4%);\n";
 
-// This opt can be used by both edge expande and edge expandv.
+// This opt can only be used by both edge expande, with multiple edge triplet.
 static constexpr const char*
     EDGE_EXPAND_E_OPT_MULTI_EDGE_NO_FILTER_TEMPLATE_STR =
         "auto %1% = gs::make_edge_expand_multie_opt<%2%>(%3%, %4%, %5%);\n";
 
+// This opt can only be used by both edge expandv, with multiplet edge triplet,
+static constexpr const char*
+    EDGE_EXPAND_V_OPT_MULTI_EDGE_NO_FILTER_TEMPLATE_STR =
+        "auto %1% = gs::make_edge_expand_multiv_opt(%2%, %3%);\n";
+
 // Expand to Edges with Filter.
 // propNames, direction, edge_label, vertex_label, filter
 static constexpr const char* EDGE_EXPAND_E_OPT_FILTER_TEMPLATE_STR =
@@ -122,6 +127,30 @@ std::string edge_label_triplet_to_array_str(
   return ss.str();
 }
 
+std::string edge_label_triplet_to_vector_str(
+    const std::vector<std::vector<int32_t>>& edge_label_triplet) {
+  std::stringstream ss;
+  ss << "std::vector<std::array<label_id_t, 3>>{";
+  for (int i = 0; i < edge_label_triplet.size(); ++i) {
+    ss << "std::array<label_id_t, 3>{";
+    if (edge_label_triplet[i].size() != 3) {
+      throw std::runtime_error("edge label triplet size must be 3");
+    }
+    for (int j = 0; j < edge_label_triplet[i].size(); ++j) {
+      ss << edge_label_triplet[i][j];
+      if (j != edge_label_triplet[i].size() - 1) {
+        ss << ", ";
+      }
+    }
+    ss << "}";
+    if (i != edge_label_triplet.size() - 1) {
+      ss << ", ";
+    }
+  }
+  ss << "}";
+  return ss.str();
+}
+
 std::string make_prop_tuple_array(const std::vector<std::string>& prop_names,
                                   const std::vector<std::string>& prop_types) {
   std::stringstream ss;
@@ -370,16 +399,31 @@ static std::pair<std::string, std::string> BuildMultiLabelEdgeExpandOpt(
   LOG(INFO) << "Find multiple edge triplet: " << edge_label_triplet.size();
 
   auto func_template_str = make_edge_expand_e_func_template_str(prop_types);
-  auto edge_triplet_2d_array =
-      edge_label_triplet_to_array_str(edge_label_triplet);
   auto edge_named_prop_array =
       make_prop_tuple_array_tuple(prop_names, prop_types);
 
-  boost::format formater(EDGE_EXPAND_E_OPT_MULTI_EDGE_NO_FILTER_TEMPLATE_STR);
+  boost::format formater;
+  if (expand_opt ==
+      physical::EdgeExpand::ExpandOpt::EdgeExpand_ExpandOpt_EDGE) {
+    auto edge_triplet_2d_array =
+        edge_label_triplet_to_array_str(edge_label_triplet);
+    formater =
+        boost::format(EDGE_EXPAND_E_OPT_MULTI_EDGE_NO_FILTER_TEMPLATE_STR);
+    formater % opt_var_name % func_template_str %
+        gs::direction_pb_to_str(direction) % edge_triplet_2d_array %
+        edge_named_prop_array;
+  } else if (expand_opt ==
+             physical::EdgeExpand::ExpandOpt::EdgeExpand_ExpandOpt_VERTEX) {
+    auto edge_triplet_2d_vector =
+        edge_label_triplet_to_vector_str(edge_label_triplet);
+    formater =
+        boost::format(EDGE_EXPAND_V_OPT_MULTI_EDGE_NO_FILTER_TEMPLATE_STR);
+    formater % opt_var_name % gs::direction_pb_to_str(direction) %
+        edge_triplet_2d_vector;
+  } else {
+    throw std::runtime_error("Unknown expand opt");
+  }
 
-  formater % opt_var_name % func_template_str %
-      gs::direction_pb_to_str(direction) % edge_triplet_2d_array %
-      edge_named_prop_array;
   return std::make_pair(opt_var_name, formater.str());
 }
 
diff --git a/flex/codegen/src/hqps/hqps_path_expand_builder.h b/flex/codegen/src/hqps/hqps_path_expand_builder.h
index d05ea4d862af..1894c95a6a65 100644
--- a/flex/codegen/src/hqps/hqps_path_expand_builder.h
+++ b/flex/codegen/src/hqps/hqps_path_expand_builder.h
@@ -117,13 +117,16 @@ class PathExpandOpBuilder {
           throw std::runtime_error("Expect edge graph type");
         }
         auto& edge_type = act_graph_type.graph_data_type();
-        if (edge_type.size() != 1) {
-          throw std::runtime_error("Expect only one edge type");
+        if (edge_type.size() == 0) {
+          throw std::runtime_error("Expect edge type size > 0");
+        }
+        std::vector<int32_t> src_labels, dst_labels;
+        for (auto i = 0; i < edge_type.size(); ++i) {
+          auto& edge_type_i = edge_type[i];
+          auto& edge_labels_i = edge_type_i.label();
+          src_labels.push_back(edge_labels_i.src_label().value());
+          dst_labels.push_back(edge_labels_i.dst_label().value());
         }
-        auto& edge_type0 = edge_type[0];
-        auto& edge_labels = edge_type0.label();
-        auto src_label = edge_labels.src_label().value();
-        auto dst_label = edge_labels.dst_label().value();
 
         // if find edge triplets, we clear current
         VLOG(10) << "Clear current dst labels:"
@@ -131,12 +134,30 @@ class PathExpandOpBuilder {
         dst_vertex_labels_.clear();
 
         if (direction_ == internal::Direction::kBoth) {
-          CHECK(src_label == dst_label);
-          dst_vertex_labels_.emplace_back(src_label);
+          // if direction is both, we need to check src_label == dst_label
+          // dedup src_labels
+          std::sort(src_labels.begin(), src_labels.end());
+          src_labels.erase(std::unique(src_labels.begin(), src_labels.end()),
+                           src_labels.end());
+          // dedup dst_labels
+          std::sort(dst_labels.begin(), dst_labels.end());
+          dst_labels.erase(std::unique(dst_labels.begin(), dst_labels.end()),
+                           dst_labels.end());
+          for (auto i = 0; i < src_labels.size(); ++i) {
+            if (src_labels[i] != dst_labels[i]) {
+              throw std::runtime_error(
+                  "Expect src_label == dst_label for both direction");
+            }
+            dst_vertex_labels_.emplace_back(dst_labels[i]);
+          }
         } else if (direction_ == internal::Direction::kOut) {
-          dst_vertex_labels_.emplace_back(dst_label);
+          for (auto i = 0; i < dst_labels.size(); ++i) {
+            dst_vertex_labels_.emplace_back(dst_labels[i]);
+          }
         } else if (direction_ == internal::Direction::kIn) {
-          dst_vertex_labels_.emplace_back(src_label);
+          for (auto i = 0; i < src_labels.size(); ++i) {
+            dst_vertex_labels_.emplace_back(src_labels[i]);
+          }
         } else {
           throw std::runtime_error("Unknown direction");
         }
@@ -175,12 +196,26 @@ class PathExpandOpBuilder {
       auto expand_opt = edge_expand_pb.expand_opt();
       CHECK(dst_vertex_labels_.size() > 0) << "no dst lables found";
 
-      physical::PhysicalOpr::MetaData meta_data;
-      // pass an empty meta_data, since we need no meta_data for
-      // edge_expand_opt.
-      std::tie(edge_expand_opt_name_, edge_expand_opt_) =
-          BuildOneLabelEdgeExpandOpt(ctx_, direction_, params,
-                                     dst_vertex_labels_, expand_opt, meta_data);
+      if (params.tables().size() < 1) {
+        throw std::runtime_error("no edge labels found");
+      } else if (params.tables().size() == 1) {
+        physical::PhysicalOpr::MetaData meta_data;
+        // pass an empty meta_data, since we need no meta_data for
+        std::tie(edge_expand_opt_name_, edge_expand_opt_) =
+            BuildOneLabelEdgeExpandOpt(ctx_, direction_, params,
+                                       dst_vertex_labels_, expand_opt,
+                                       meta_data);
+      } else {
+        // get the first meta_data
+        if (meta_data_pb.size() < 1) {
+          throw std::runtime_error("no meta_data found");
+        }
+        auto& meta_data = meta_data_pb[0];
+        std::tie(edge_expand_opt_name_, edge_expand_opt_) =
+            BuildMultiLabelEdgeExpandOpt(ctx_, direction_, params, expand_opt,
+                                         meta_data);
+      }
+
       VLOG(10) << "edge_expand_opt_name_: " << edge_expand_opt_name_;
       VLOG(10) << "edge_expand_opt_: " << edge_expand_opt_;
     }
diff --git a/flex/engines/hqps_db/core/operator/edge_expand.h b/flex/engines/hqps_db/core/operator/edge_expand.h
index fdd7d147207d..c22428519d10 100644
--- a/flex/engines/hqps_db/core/operator/edge_expand.h
+++ b/flex/engines/hqps_db/core/operator/edge_expand.h
@@ -309,11 +309,11 @@ class EdgeExpand {
   /// @param v_sets
   /// @param edge_expand_opt
   /// @return
-  template <typename... SELECTOR,
+  template <typename... SELECTOR, typename... SET_T,
             typename RES_T = std::pair<vertex_set_t, std::vector<offset_t>>>
   static RES_T EdgeExpandV(
       const GRAPH_INTERFACE& graph,
-      const GeneralVertexSet<vertex_id_t, label_id_t>& cur_vertex_set,
+      const GeneralVertexSet<vertex_id_t, label_id_t, SET_T...>& cur_vertex_set,
       Direction direction, label_id_t edge_label, label_id_t other_label,
       Filter<TruePredicate, SELECTOR...>&& edge_filter,
       size_t limit = INT_MAX) {
@@ -440,11 +440,12 @@ class EdgeExpand {
   /// @param v_sets
   /// @param edge_expand_opt
   /// @return
-  template <
-      typename VERTEX_SET_T, size_t num_labels, typename EDGE_FILTER_T,
-      size_t... Is, typename std::enable_if<(num_labels != 2)>::type* = nullptr,
-      typename RES_T = std::pair<GeneralVertexSet<vertex_id_t, label_id_t>,
-                                 std::vector<offset_t>>>
+  template <typename VERTEX_SET_T, size_t num_labels, typename EDGE_FILTER_T,
+            typename... SET_T, size_t... Is,
+            typename std::enable_if<(num_labels != 2)>::type* = nullptr,
+            typename RES_T =
+                std::pair<GeneralVertexSet<vertex_id_t, label_id_t, SET_T...>,
+                          std::vector<offset_t>>>
   static RES_T EdgeExpandV(const GRAPH_INTERFACE& graph,
                            const VERTEX_SET_T& cur_vertex_set,
                            Direction direction, label_id_t edge_label,
@@ -499,7 +500,7 @@ class EdgeExpand {
     }
     CHECK(cur_ind == total_size);
     auto copied_labels(other_labels);
-    GeneralVertexSet<vertex_id_t, label_id_t> res_set(
+    GeneralVertexSet<vertex_id_t, label_id_t, SET_T...> res_set(
         std::move(res_vids), std::move(copied_labels), std::move(res_bitset));
 
     return std::make_pair(std::move(res_set), std::move(res_offset));
@@ -602,10 +603,10 @@ class EdgeExpand {
   /// @param edge_filter
   /// @param limit
   /// @return /
-  template <size_t num_pairs, typename... PropTuple>
+  template <size_t num_pairs, typename... PropTuple, typename... SET_T>
   static auto EdgeExpandVMultiTriplet(
       const GRAPH_INTERFACE& graph,
-      const GeneralVertexSet<vertex_id_t, label_id_t>& cur_vertex_set,
+      const GeneralVertexSet<vertex_id_t, label_id_t, SET_T...>& cur_vertex_set,
       Direction& direction,
       std::array<std::array<label_id_t, 3>, num_pairs>& edge_labels,
       std::tuple<PropTupleArrayT<PropTuple>...>& prop_names,
@@ -833,10 +834,10 @@ class EdgeExpand {
   /// @param edge_filter
   /// @param limit
   /// @return /
-  template <size_t num_pairs, typename... PropTuple>
+  template <size_t num_pairs, typename... PropTuple, typename... SET_T>
   static auto EdgeExpandEMultiTriplet(
       const GRAPH_INTERFACE& graph,
-      const GeneralVertexSet<vertex_id_t, label_id_t>& cur_vertex_set,
+      const GeneralVertexSet<vertex_id_t, label_id_t, SET_T...>& cur_vertex_set,
       Direction& direction,
       std::array<std::array<label_id_t, 3>, num_pairs>& edge_labels,
       std::tuple<PropTupleArrayT<PropTuple>...>& prop_names,
diff --git a/flex/engines/hqps_db/core/operator/path_expand.h b/flex/engines/hqps_db/core/operator/path_expand.h
index 683f723392af..0aad0c3b72e7 100644
--- a/flex/engines/hqps_db/core/operator/path_expand.h
+++ b/flex/engines/hqps_db/core/operator/path_expand.h
@@ -180,7 +180,7 @@ class PathExpand {
     return std::make_pair(std::move(row_vertex_set), std::move(res_offsets));
   }
 
-  // PathExpandV for row vertex set as input.
+  // PathExpandV for row vertex set as input, retrieve no properties.
   template <typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
             typename EDGE_FILTER_T, typename RES_SET_T = vertex_set_t<Dist>,
             typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
@@ -207,6 +207,161 @@ class PathExpand {
                           std::move(std::get<2>(tuple)));
   }
 
+  // PathExpandV with multiple dst labels, for row vertex set as input, output
+  // no properties.
+  template <
+      typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
+      size_t num_labels, typename EDGE_FILTER_T, size_t get_v_num_labels,
+      typename RES_SET_T = GeneralVertexSet<vertex_id_t, label_id_t, Dist>,
+      typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  static RES_T PathExpandV(
+      const GRAPH_INTERFACE& graph,
+      const RowVertexSet<LabelT, vertex_id_t, V_SET_T...>& vertex_set,
+      PathExpandVMultiDstOpt<LabelT, num_labels, EDGE_FILTER_T,
+                             get_v_num_labels, VERTEX_FILTER_T>&&
+          path_expand_opt) {
+    //
+    auto cur_label = vertex_set.GetLabel();
+    auto& range = path_expand_opt.range_;
+    auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
+    auto& get_v_opt = path_expand_opt.get_v_opt_;
+    return PathExpandMultiDstV(graph, cur_label, vertex_set.GetVertices(),
+                               range, edge_expand_opt, get_v_opt);
+  }
+
+  // PathExpandV with multiple dst labels, for twoLabelSet vertex set as input,
+  // output no properties.
+  template <
+      typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
+      size_t num_labels, typename EDGE_FILTER_T, size_t get_v_num_labels,
+      typename RES_SET_T = GeneralVertexSet<vertex_id_t, label_id_t, Dist>,
+      typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  static RES_T PathExpandV(
+      const GRAPH_INTERFACE& graph,
+      const TwoLabelVertexSet<vertex_id_t, label_id_t, V_SET_T...>& vertex_set,
+      PathExpandVMultiDstOpt<LabelT, num_labels, EDGE_FILTER_T,
+                             get_v_num_labels, VERTEX_FILTER_T>&&
+          path_expand_opt) {
+    auto& range = path_expand_opt.range_;
+    auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
+    auto& get_v_opt = path_expand_opt.get_v_opt_;
+    auto src_label_vec = vertex_set.GetLabelVec();
+    auto src_label_set = array_to_vec(vertex_set.GetLabels());
+    auto src_label_id_vec = label_key_vec_2_label_id_vec(src_label_vec);
+    return PathExpandMultiDstVFromGeneralSet(graph, vertex_set.GetVertices(),
+                                             src_label_set, src_label_id_vec,
+                                             range, edge_expand_opt, get_v_opt);
+  }
+
+  // PathExpandV with multiple dst labels, for general vertex set as input,
+  // output no properties.
+  template <
+      typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
+      size_t num_labels, typename EDGE_FILTER_T, size_t get_v_num_labels,
+      typename RES_SET_T = GeneralVertexSet<vertex_id_t, label_id_t, Dist>,
+      typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  static RES_T PathExpandV(
+      const GRAPH_INTERFACE& graph,
+      const GeneralVertexSet<vertex_id_t, label_id_t, V_SET_T...>& vertex_set,
+      PathExpandVMultiDstOpt<LabelT, num_labels, EDGE_FILTER_T,
+                             get_v_num_labels, VERTEX_FILTER_T>&&
+          path_expand_opt) {
+    auto& range = path_expand_opt.range_;
+    auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
+    auto& get_v_opt = path_expand_opt.get_v_opt_;
+    auto& bitsets = vertex_set.GetBitsets();
+    auto src_label_set = vertex_set.GetLabels();
+    auto src_label_vec = vertex_set.GetLabelVec();
+    auto src_label_id_vec = label_key_vec_2_label_id_vec(src_label_vec);
+    return PathExpandMultiDstVFromGeneralSet(graph, vertex_set.GetVertices(),
+                                             src_label_set, src_label_id_vec,
+                                             range, edge_expand_opt, get_v_opt);
+  }
+
+  // PathExpandV with multiple edge triplet as input, output no properties.
+  template <
+      typename VERTEX_SET_T, typename LabelT, typename VERTEX_FILTER_T,
+      typename EDGE_FILTER_T, size_t get_v_num_labels,
+      typename RES_SET_T = GeneralVertexSet<vertex_id_t, label_id_t, Dist>,
+      typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  static RES_T PathExpandVMultiTriplet(
+      const GRAPH_INTERFACE& graph, const VERTEX_SET_T& vertex_set,
+      PathExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T, get_v_num_labels,
+                                 VERTEX_FILTER_T>&& path_expand_opt) {
+    auto& range = path_expand_opt.range_;
+    auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
+    auto& get_v_opt = path_expand_opt.get_v_opt_;
+    auto& edge_triplets = edge_expand_opt.edge_label_triplets_;
+    auto& vertex_other_labels = get_v_opt.v_labels_;
+    auto vertex_other_labels_vec = array_to_vec(vertex_other_labels);
+
+    std::vector<vertex_id_t> res_vertices;
+    std::vector<offset_t> res_offsets;
+    std::vector<Dist> res_dists;
+    std::vector<label_id_t> res_labels_vec;
+    if constexpr (VERTEX_SET_T::is_row_vertex_set) {
+      auto src_label = vertex_set.GetLabel();
+      auto& vertices_vec = vertex_set.GetVertices();
+      std::vector<label_id_t> src_labels_vec(vertices_vec.size(),
+                                             vertex_set.GetLabel());
+      std::vector<label_id_t> src_labels_set{src_label};
+      // expand from row_vertex_set;
+      std::tie(res_vertices, res_dists, res_labels_vec, res_offsets) =
+          path_expandv_multi_triplet(graph, edge_triplets,
+                                     vertex_other_labels_vec,
+                                     edge_expand_opt.direction_, vertices_vec,
+                                     src_labels_set, src_labels_vec, range);
+
+    } else if constexpr (VERTEX_SET_T::is_two_label_set) {
+      // expand from two_label_vertex_set;
+      auto& vertices_vec = vertex_set.GetVertices();
+      auto src_label_vec = vertex_set.GetLabelVec();
+      auto src_label_set = array_to_vec(vertex_set.GetLabels());
+      auto src_label_id_vec = label_key_vec_2_label_id_vec(src_label_vec);
+      std::tie(res_vertices, res_dists, res_labels_vec, res_offsets) =
+          path_expandv_multi_triplet(graph, edge_triplets,
+                                     vertex_other_labels_vec,
+                                     edge_expand_opt.direction_, vertices_vec,
+                                     src_label_set, src_label_id_vec, range);
+
+    } else if constexpr (VERTEX_SET_T::is_general_set) {
+      // expand from general_vertex_set;
+      auto src_label_set = vertex_set.GetLabels();
+      auto src_label_vec = vertex_set.GetLabelVec();
+      auto src_label_id_vec = label_key_vec_2_label_id_vec(src_label_vec);
+      std::tie(res_vertices, res_dists, res_labels_vec, res_offsets) =
+          path_expandv_multi_triplet(
+              graph, edge_triplets, vertex_other_labels_vec,
+              edge_expand_opt.direction_, vertex_set.GetVertices(),
+              src_label_set, src_label_id_vec, range);
+    } else {
+      // fail at compile time.
+      static_assert(VERTEX_SET_T::is_row_vertex_set ||
+                        VERTEX_SET_T::is_two_label_set ||
+                        VERTEX_SET_T::is_general_set,
+                    "Unsupported vertex set type");
+    }
+
+    auto res_dist_tuple = single_col_vec_to_tuple_vec(std::move(res_dists));
+    std::vector<grape::Bitset> res_bitsets;
+    std::vector<label_id_t> label_id_vec;
+    std::tie(res_bitsets, label_id_vec) =
+        convert_label_id_vec_to_bitsets(res_labels_vec);
+    if (label_id_vec.size() > vertex_other_labels.size()) {
+      LOG(ERROR) << "Error state: label_id_vec.size(): " << label_id_vec.size()
+                 << ", vertex_other_labels.size(): "
+                 << vertex_other_labels.size();
+    }
+    if (res_bitsets.size() != label_id_vec.size()) {
+      LOG(ERROR) << "Error state: res_bitsets.size(): " << res_bitsets.size()
+                 << ", label_id_vec.size(): " << label_id_vec.size();
+    }
+    auto set = make_general_set(
+        std::move(res_vertices), std::move(res_dist_tuple), {"dist"},
+        std::move(label_id_vec), std::move(res_bitsets));
+    return std::make_pair(std::move(set), std::move(res_offsets));
+  }
+
   template <typename LabelT, typename EDGE_FILTER_T, typename... SELECTOR>
   static std::tuple<std::vector<vertex_id_t>, std::vector<Dist>,
                     std::vector<offset_t>>
@@ -301,7 +456,6 @@ class PathExpand {
     }
     std::vector<std::vector<vertex_id_t>> gids;
     std::vector<std::vector<offset_t>> offsets;
-    std::unordered_set<vertex_id_t> visited_vertices;
 
     gids.resize(range.limit_);
     offsets.resize(range.limit_);
@@ -317,7 +471,6 @@ class PathExpand {
       offsets[0].emplace_back(i);
     }
     offsets[0].emplace_back(src_vertices_size);
-    visited_vertices.insert(src_vertices_vec.begin(), src_vertices_vec.end());
 
     double visit_array_time = 0.0;
     for (auto cur_hop = 1; cur_hop < range.limit_; ++cur_hop) {
@@ -378,6 +531,126 @@ class PathExpand {
   }
 
  private:
+  // Expand V from single label vertices, only take vertices.
+  // Collect multiple dst label vertices.
+  template <typename EDGE_FILTER_FUNC, size_t num_labels,
+            typename VERTEX_FILTER_T, size_t get_v_num_labels>
+  static auto PathExpandMultiDstV(
+      const GRAPH_INTERFACE& graph, label_id_t src_label,
+      const std::vector<vertex_id_t>& vertices_vec, const Range& range,
+      const EdgeExpandOptMultiLabel<label_id_t, num_labels, EDGE_FILTER_FUNC>&
+          edge_opt,
+      const GetVOpt<label_id_t, get_v_num_labels, VERTEX_FILTER_T>& get_vopt) {
+    // We suppose VERTEX_FILTER is true
+    auto& edge_other_labels = edge_opt.other_labels_;
+    auto& vertex_other_labels = get_vopt.v_labels_;
+    auto edge_other_labels_vec = array_to_vec(edge_other_labels);
+    auto vertex_other_labels_vec = array_to_vec(vertex_other_labels);
+    auto edge_label = edge_opt.edge_label_;
+    std::vector<vertex_id_t> res_vertices;
+    std::vector<offset_t> res_offsets;
+    std::vector<Dist> res_dists;
+    std::vector<label_id_t> res_labels_vec;
+    std::vector<label_id_t> src_labels_vec(vertices_vec.size(), src_label);
+    std::vector<label_id_t> src_labels_set{src_label};
+    std::vector<std::array<label_id_t, 3>> edge_triplets;
+    // Since other_labels is extracted when code generating, we reverse back.
+    if (edge_opt.direction_ == Direction::Out) {
+      for (auto& other_label : edge_other_labels) {
+        edge_triplets.emplace_back(
+            std::array<label_id_t, 3>{src_label, other_label, edge_label});
+      }
+    } else {  // In and both.
+      for (auto& other_label : edge_other_labels) {
+        edge_triplets.emplace_back(
+            std::array<label_id_t, 3>{other_label, src_label, edge_label});
+      }
+    }
+    std::tie(res_vertices, res_dists, res_labels_vec, res_offsets) =
+        path_expandv_multi_triplet(
+            graph, edge_triplets, vertex_other_labels_vec, edge_opt.direction_,
+            vertices_vec, src_labels_set, src_labels_vec, range);
+    auto res_dist_tuple = single_col_vec_to_tuple_vec(std::move(res_dists));
+    std::vector<grape::Bitset> res_bitsets;
+    std::vector<label_id_t> label_id_vec;
+    std::tie(res_bitsets, label_id_vec) =
+        convert_label_id_vec_to_bitsets(res_labels_vec);
+    if (label_id_vec.size() > vertex_other_labels.size()) {
+      LOG(ERROR) << "Error state: label_id_vec.size(): " << label_id_vec.size()
+                 << ", vertex_other_labels.size(): "
+                 << vertex_other_labels.size();
+    }
+    if (res_bitsets.size() != label_id_vec.size()) {
+      LOG(ERROR) << "Error state: res_bitsets.size(): " << res_bitsets.size()
+                 << ", label_id_vec.size(): " << label_id_vec.size();
+    }
+    auto set = make_general_set(
+        std::move(res_vertices), std::move(res_dist_tuple), {"dist"},
+        std::move(label_id_vec), std::move(res_bitsets));
+    return std::make_pair(std::move(set), std::move(res_offsets));
+  }
+
+  // Expand V from two label vertices.
+  // Collect multiple dst label vertices.
+  template <typename EDGE_FILTER_FUNC, size_t num_labels,
+            typename VERTEX_FILTER_T, size_t get_v_num_labels>
+  static auto PathExpandMultiDstVFromGeneralSet(
+      const GRAPH_INTERFACE& graph,
+      const std::vector<vertex_id_t>& vertices_vec,
+      const std::vector<label_id_t>& src_labels_set,
+      const std::vector<label_id_t>& src_labels_vec, const Range& range,
+      const EdgeExpandOptMultiLabel<label_id_t, num_labels, EDGE_FILTER_FUNC>&
+          edge_opt,
+      const GetVOpt<label_id_t, get_v_num_labels, VERTEX_FILTER_T>& get_vopt) {
+    auto& edge_other_labels = edge_opt.other_labels_;
+    auto& vertex_other_labels = get_vopt.v_labels_;
+    auto edge_other_labels_vec = array_to_vec(edge_other_labels);
+    auto vertex_other_labels_vec = array_to_vec(vertex_other_labels);
+    auto edge_label = edge_opt.edge_label_;
+    std::vector<vertex_id_t> res_vertices;
+    std::vector<offset_t> res_offsets;
+    std::vector<Dist> res_dists;
+    std::vector<label_id_t> res_labels_vec;
+    std::vector<std::array<label_id_t, 3>> edge_triplets;
+    if (edge_opt.direction_ == Direction::Out) {
+      for (auto& src_label : src_labels_set) {
+        for (auto& other_label : edge_other_labels_vec) {
+          edge_triplets.emplace_back(
+              std::array<label_id_t, 3>{src_label, other_label, edge_label});
+        }
+      }
+    } else {
+      for (auto& src_label : src_labels_set) {
+        for (auto& other_label : edge_other_labels_vec) {
+          edge_triplets.emplace_back(
+              std::array<label_id_t, 3>{other_label, src_label, edge_label});
+        }
+      }
+    }
+    std::tie(res_vertices, res_dists, res_labels_vec, res_offsets) =
+        path_expandv_multi_triplet(
+            graph, edge_triplets, vertex_other_labels_vec, edge_opt.direction_,
+            vertices_vec, src_labels_set, src_labels_vec, range);
+    auto res_dist_tuple = single_col_vec_to_tuple_vec(std::move(res_dists));
+    std::vector<grape::Bitset> res_bitsets;
+    std::vector<label_id_t> label_id_vec;
+    std::tie(res_bitsets, label_id_vec) =
+        convert_label_id_vec_to_bitsets(res_labels_vec);
+    if (label_id_vec.size() > vertex_other_labels.size()) {
+      LOG(ERROR) << "Error state: label_id_vec.size(): " << label_id_vec.size()
+                 << ", vertex_other_labels.size(): "
+                 << vertex_other_labels.size();
+    }
+    if (res_bitsets.size() != label_id_vec.size()) {
+      LOG(ERROR) << "Error state: res_bitsets.size(): " << res_bitsets.size()
+                 << ", label_id_vec.size(): " << label_id_vec.size();
+    }
+    auto set = make_general_set(
+        std::move(res_vertices), std::move(res_dist_tuple), {"dist"},
+        std::move(label_id_vec), std::move(res_bitsets));
+    return std::make_pair(std::move(set), std::move(res_offsets));
+  }
+
   // Expand Path from single label vertices, only take vertices.
   template <typename EDGE_FILTER_FUNC, typename VERTEX_FILTER_T,
             typename... EDATA_T>
@@ -465,6 +738,264 @@ class PathExpand {
     return std::make_pair(std::move(path_set), std::move(ctx_offsets));
   }
 
+  // expand from vertices, with multiple edge triplets.
+  // The intermediate vertices can also have multiple labels, and expand with
+  // multiple edge triplet.
+  static auto path_expandv_multi_triplet(
+      const GRAPH_INTERFACE& graph,
+      const std::vector<std::array<label_id_t, 3>>&
+          edge_label_triplets,  // src, dst, edge
+      const std::vector<label_id_t>& get_v_labels, const Direction& direction,
+      const std::vector<vertex_id_t>& vertices_vec,
+      const std::vector<label_id_t>& src_labels_set,
+      const std::vector<label_id_t>& src_v_labels_vec, const Range& range) {
+    // (range, other_label_ind, vertices)
+    LOG(INFO) << "PathExpandV with multiple edge triplets: "
+              << gs::to_string(edge_label_triplets)
+              << ", direction: " << gs::to_string(direction)
+              << ", vertices size: " << vertices_vec.size()
+              << ", src_labels_set: " << gs::to_string(src_labels_set)
+              << ", range: " << range.start_ << ", " << range.limit_;
+    std::vector<std::vector<vertex_id_t>> other_vertices;
+    std::vector<std::vector<label_id_t>> other_labels_vec;
+    std::vector<std::vector<offset_t>> other_offsets;
+    other_vertices.resize(range.limit_);
+    other_offsets.resize(range.limit_);
+    other_labels_vec.resize(range.limit_);
+    for (auto i = 0; i < range.limit_; ++i) {
+      other_offsets[i].reserve(vertices_vec.size() + 1);
+    }
+    other_vertices[0].insert(other_vertices[0].end(), vertices_vec.begin(),
+                             vertices_vec.end());
+    other_labels_vec[0] = src_v_labels_vec;
+    for (auto i = 0; i < vertices_vec.size(); ++i) {
+      other_offsets[0].emplace_back(i);
+    }
+    other_offsets[0].emplace_back(vertices_vec.size());
+    // the input vertices can have many labels. Should be the union of
+    // src_vertex_labels and other_labels.
+    std::vector<label_id_t> src_label_candidates;
+    {
+      // insert src and dst labels in edge_label_triplets to
+      // src_label_candidates
+      for (auto& edge_label_triplet : edge_label_triplets) {
+        auto src_label = edge_label_triplet[0];
+        auto dst_label = edge_label_triplet[1];
+        src_label_candidates.emplace_back(src_label);
+        src_label_candidates.emplace_back(dst_label);
+      }
+      std::sort(src_label_candidates.begin(), src_label_candidates.end());
+      // dedup
+      auto last =
+          std::unique(src_label_candidates.begin(), src_label_candidates.end());
+      src_label_candidates.erase(last, src_label_candidates.end());
+    }
+    VLOG(10) << "src_label_candidates: " << gs::to_string(src_label_candidates);
+    // iterate for all hops
+    for (auto cur_hop = 1; cur_hop < range.limit_; ++cur_hop) {
+      using nbr_list_type =
+          std::pair<std::vector<gs::mutable_csr_graph_impl::Nbr>, label_id_t>;
+      std::vector<std::vector<nbr_list_type>> nbr_lists;
+      nbr_lists.resize(other_vertices[cur_hop - 1].size());
+      std::vector<bool> indicator(other_vertices[cur_hop - 1].size(), false);
+
+      for (auto& src_other_label : src_label_candidates) {
+        // for each kind of src vertices, try each edge triplet.
+        label_id_t dst_other_label;
+        for (auto& edge_triplet : edge_label_triplets) {
+          if (direction == Direction::In) {
+            if (src_other_label != edge_triplet[1]) {
+              continue;
+            } else {
+              dst_other_label = edge_triplet[0];
+            }
+          } else if (direction == Direction::Out) {
+            if (src_other_label != edge_triplet[0]) {
+              continue;
+            } else {
+              dst_other_label = edge_triplet[1];
+            }
+          } else {
+            // both
+            if (src_other_label != edge_triplet[0] &&
+                src_other_label != edge_triplet[1]) {
+              continue;
+            } else {
+              if (src_other_label == edge_triplet[0]) {
+                dst_other_label = edge_triplet[1];
+              } else {
+                dst_other_label = edge_triplet[0];
+              }
+            }
+          }
+          auto cur_edge_label = edge_triplet[2];
+
+          std::vector<size_t> indices;
+
+          std::vector<vertex_id_t> other_vertices_for_cur_label;
+          std::tie(other_vertices_for_cur_label, indices) =
+              get_vertices_with_label(other_vertices[cur_hop - 1],
+                                      other_labels_vec[cur_hop - 1],
+                                      src_other_label);
+          if (indices.size() > 0) {
+            VLOG(10) << "Get vertices with label: "
+                     << std::to_string(src_other_label) << ", "
+                     << other_vertices_for_cur_label.size();
+
+            label_id_t real_src_label, real_dst_label;
+            if (direction == Direction::Out) {
+              real_src_label = src_other_label;
+              real_dst_label = dst_other_label;
+            } else {
+              // in or both.
+              real_src_label = dst_other_label;
+              real_dst_label = src_other_label;
+            }
+            auto cur_nbr_list = graph.GetOtherVertices(
+                real_src_label, real_dst_label, cur_edge_label,
+                other_vertices_for_cur_label, gs::to_string(direction),
+                INT_MAX);
+            {
+              size_t tmp_sum = 0;
+              for (auto i = 0; i < cur_nbr_list.size(); ++i) {
+                tmp_sum += cur_nbr_list.get_vector(i).size();
+              }
+              VLOG(10) << "Get other vertices: " << cur_nbr_list.size()
+                       << ", nbr size: " << tmp_sum
+                       << ", from: " << std::to_string(real_src_label)
+                       << ", to: " << std::to_string(real_dst_label)
+                       << ", dst other_label: "
+                       << std::to_string(dst_other_label)
+                       << ", edge_label: " << std::to_string(cur_edge_label)
+                       << ", direction: " << gs::to_string(direction);
+            }
+
+            for (auto i = 0; i < indices.size(); ++i) {
+              auto index = indices[i];
+              nbr_lists[index].emplace_back(cur_nbr_list.get_vector(i),
+                                            dst_other_label);
+              indicator[index] = true;
+            }
+
+          } else {
+            VLOG(10) << "No vertices with label: "
+                     << std::to_string(src_other_label);
+          }
+        }
+      }
+      // extract vertices from nbrs, and add them to other_vertices[cur_hop]
+      // and update other_offset
+      auto& cur_other_vertices = other_vertices[cur_hop];
+      auto& cur_other_offsets =
+          other_offsets[cur_hop];  // other_offset is always aligned with
+                                   // src_vertices.
+      auto& cur_other_labels_vec = other_labels_vec[cur_hop];
+      size_t cur_hop_new_vnum = 0;
+      for (auto i = 0; i < nbr_lists.size(); ++i) {
+        for (auto& nbr_list_pair : nbr_lists[i]) {
+          cur_hop_new_vnum += nbr_list_pair.first.size();
+        }
+      }
+      VLOG(10) << "cur_hop_new_vnum: " << cur_hop_new_vnum;
+      cur_other_vertices.reserve(cur_hop_new_vnum);
+      // cur_other_offsets.reserve(cur_hop_new_vnum);
+      cur_other_labels_vec.reserve(cur_hop_new_vnum);
+      cur_other_offsets.reserve(vertices_vec.size() + 1);
+      // cur_other_offsets.emplace_back(0);
+      std::vector<offset_t> tmp_cur_offset;
+      tmp_cur_offset.reserve(cur_hop_new_vnum);
+      tmp_cur_offset.emplace_back(0);
+      size_t cur_cnt = 0;
+      for (auto i = 0; i < nbr_lists.size(); ++i) {
+        for (auto& nbr_list_pair : nbr_lists[i]) {
+          auto cur_other_vertex_label = nbr_list_pair.second;
+          auto& nbr_list = nbr_list_pair.first;
+          for (auto j = 0; j < nbr_list.size(); ++j) {
+            auto& nbr = nbr_list[j];
+            cur_other_vertices.emplace_back(nbr.neighbor());
+            cur_other_labels_vec.emplace_back(cur_other_vertex_label);
+          }
+          cur_cnt += nbr_list.size();
+        }
+        tmp_cur_offset.emplace_back(cur_cnt);
+      }
+      for (auto i = 0; i < other_offsets[cur_hop - 1].size(); ++i) {
+        other_offsets[cur_hop].emplace_back(
+            tmp_cur_offset[other_offsets[cur_hop - 1][i]]);
+      }
+    }
+
+    // select vertices that are in range and are in vertex_other_labels.
+    std::vector<vertex_id_t> res_vertices;
+    std::vector<offset_t> res_offsets;
+    std::vector<Dist> res_dists;
+    std::vector<label_id_t> res_labels_vec;
+    std::vector<bool> valid_labels(sizeof(label_id_t) * 8, false);
+    for (auto& v_label : get_v_labels) {
+      valid_labels[v_label] = true;
+    }
+    auto num_valid_labels =
+        std::accumulate(valid_labels.begin(), valid_labels.end(), 0);
+    VLOG(10) << "Select vertices within " << num_valid_labels
+             << " valid labels, from " << get_v_labels.size();
+
+    size_t flat_size = 0;
+    for (auto i = range.start_; i < range.limit_; ++i) {
+      flat_size += other_vertices[i].size();
+    }
+    VLOG(10) << "PathExpandV with multiple triplet flat size: " << flat_size;
+    res_vertices.reserve(flat_size);
+    res_dists.reserve(flat_size);
+    res_labels_vec.reserve(flat_size);
+    res_offsets.reserve(vertices_vec.size() + 1);
+    res_offsets.emplace_back(0);
+    for (auto i = 0; i < vertices_vec.size(); ++i) {
+      for (auto j = range.start_; j < range.limit_; ++j) {
+        auto start = other_offsets[j][i];
+        auto end = other_offsets[j][i + 1];
+        for (auto k = start; k < end; ++k) {
+          auto gid = other_vertices[j][k];
+          auto label = other_labels_vec[j][k];
+          if (valid_labels[label]) {
+            res_vertices.emplace_back(gid);
+            res_dists.emplace_back(j);
+            res_labels_vec.emplace_back(label);
+          }
+        }
+      }
+      res_offsets.emplace_back(res_vertices.size());
+    }
+    return std::make_tuple(std::move(res_vertices), std::move(res_dists),
+                           std::move(res_labels_vec), std::move(res_offsets));
+  }
+
+  // returns the vector of valid labels and the bitsets.
+  static std::pair<std::vector<grape::Bitset>, std::vector<label_id_t>>
+  convert_label_id_vec_to_bitsets(const std::vector<label_id_t>& label_vec) {
+    // convert label_id_vec to bitsets.
+    std::vector<grape::Bitset> res_bitsets;
+    std::vector<label_id_t> res_label_id_vec;
+    std::vector<int32_t>
+        label_to_index;  // label to index in res_bitsets vector.
+    label_to_index.resize(sizeof(label_id_t) * 8, -1);
+    for (auto i = 0; i < label_vec.size(); ++i) {
+      if (label_to_index[label_vec[i]] == -1) {
+        label_to_index[label_vec[i]] = res_bitsets.size();
+        res_bitsets.emplace_back();
+        res_bitsets.back().init(label_vec.size());
+        res_label_id_vec.emplace_back(label_vec[i]);
+      }
+    }
+    auto num_valid_labels = res_bitsets.size();
+    VLOG(10) << "num valid labels: " << num_valid_labels;
+
+    for (auto i = 0; i < label_vec.size(); ++i) {
+      auto index = label_to_index[label_vec[i]];
+      res_bitsets[index].set_bit(i);
+    }
+    return std::make_pair(std::move(res_bitsets), std::move(res_label_id_vec));
+  }
+
   template <typename T, typename... Ts>
   static auto prepend_tuple(std::vector<T>&& first_col,
                             std::vector<std::tuple<Ts...>>&& old_cols) {
@@ -487,6 +1018,31 @@ class PathExpand {
     }
     return res_vec;
   }
+
+  static std::pair<std::vector<vertex_id_t>, std::vector<size_t>>
+  get_vertices_with_label(const std::vector<vertex_id_t>& vertices,
+                          const std::vector<label_id_t>& label_vec,
+                          const label_id_t query_label) {
+    std::vector<vertex_id_t> res_vertices;
+    std::vector<size_t> indices;
+    for (auto i = 0; i < label_vec.size(); ++i) {
+      if (label_vec[i] == query_label) {
+        res_vertices.emplace_back(vertices[i]);
+        indices.emplace_back(i);
+      }
+    }
+    return std::make_pair(std::move(res_vertices), std::move(indices));
+  }
+
+  static std::vector<label_id_t> label_key_vec_2_label_id_vec(
+      const std::vector<LabelKey>& label_key_vec) {
+    std::vector<label_id_t> res_vec;
+    res_vec.reserve(label_key_vec.size());
+    for (auto& label_key : label_key_vec) {
+      res_vec.emplace_back(label_key.label_id);
+    }
+    return res_vec;
+  }
 };
 
 }  // namespace gs
diff --git a/flex/engines/hqps_db/core/operator/project.h b/flex/engines/hqps_db/core/operator/project.h
index 0f60d0a7c213..a2b48655761c 100644
--- a/flex/engines/hqps_db/core/operator/project.h
+++ b/flex/engines/hqps_db/core/operator/project.h
@@ -407,10 +407,11 @@ class ProjectOp {
 
   // general vertex set.
   template <
-      typename T, typename VID_T, typename LabelT,
+      typename T, typename VID_T, typename LabelT, typename... SET_T,
       typename std::enable_if<(!std::is_same_v<T, LabelKey>)>::type* = nullptr>
   static auto apply_single_project_impl(
-      const GRAPH_INTERFACE& graph, GeneralVertexSet<VID_T, LabelT>& node,
+      const GRAPH_INTERFACE& graph,
+      GeneralVertexSet<VID_T, LabelT, SET_T...>& node,
       const std::string& prop_name_, const std::vector<size_t>& repeat_array) {
     VLOG(10) << "start fetching properties";
     auto tmp_prop_vec = get_property_tuple_general<T>(
diff --git a/flex/engines/hqps_db/core/operator/scan.h b/flex/engines/hqps_db/core/operator/scan.h
index 5a67c1fc07a4..723600612953 100644
--- a/flex/engines/hqps_db/core/operator/scan.h
+++ b/flex/engines/hqps_db/core/operator/scan.h
@@ -50,10 +50,10 @@ class Scan {
   }
 
   template <typename EXPR, typename... SELECTOR, size_t num_labels>
-  static GeneralVertexSet<vertex_id_t, label_id_t> ScanMultiLabelVertex(
-      const GRAPH_INTERFACE& graph,
-      const std::array<label_id_t, num_labels>& labels,
-      Filter<EXPR, SELECTOR...>&& filter) {
+  static GeneralVertexSet<vertex_id_t, label_id_t, grape::EmptyType>
+  ScanMultiLabelVertex(const GRAPH_INTERFACE& graph,
+                       const std::array<label_id_t, num_labels>& labels,
+                       Filter<EXPR, SELECTOR...>&& filter) {
     auto expr = filter.expr_;
     auto selectors = filter.selectors_;
     return scan_multi_label_vertex_with_selector(graph, labels, expr,
@@ -139,7 +139,7 @@ class Scan {
 
  private:
   template <typename EXPR, typename... SELECTOR, size_t num_labels>
-  static GeneralVertexSet<vertex_id_t, label_id_t>
+  static GeneralVertexSet<vertex_id_t, label_id_t, grape::EmptyType>
   scan_multi_label_vertex_with_selector(
       const GRAPH_INTERFACE& graph,
       const std::array<label_id_t, num_labels>& labels, const EXPR& expr,
diff --git a/flex/engines/hqps_db/core/operator/sink.h b/flex/engines/hqps_db/core/operator/sink.h
index a054d6d7549c..9181d6f07923 100644
--- a/flex/engines/hqps_db/core/operator/sink.h
+++ b/flex/engines/hqps_db/core/operator/sink.h
@@ -736,16 +736,16 @@ class SinkOp {
   }
 
   // sink general vertex, we only return vertex ids.
-  template <size_t Ind, size_t act_tag_id, typename VID_T, typename LabelT>
-  static void sink_col_impl(const GRAPH_INTERFACE& graph,
-                            results::CollectiveResults& results_vec,
-                            const GeneralVertexSet<VID_T, LabelT>& vertex_set,
-                            const std::vector<size_t>& repeat_offsets,
-                            int32_t tag_id) {
+  template <size_t Ind, size_t act_tag_id, typename VID_T, typename LabelT,
+            typename... SET_T>
+  static void sink_col_impl(
+      const GRAPH_INTERFACE& graph, results::CollectiveResults& results_vec,
+      const GeneralVertexSet<VID_T, LabelT, SET_T...>& vertex_set,
+      const std::vector<size_t>& repeat_offsets, int32_t tag_id) {
     auto& schema = graph.schema();
     auto vertices_vec = vertex_set.GetVertices();
     auto labels_vec = vertex_set.GetLabels();
-    auto bitsets = vertex_set.GetBitsets();
+    auto& bitsets = vertex_set.GetBitsets();
     CHECK(vertices_vec.size() == labels_vec.size());
     std::vector<std::vector<std::string>> prop_names;
     for (auto i = 0; i < labels_vec.size(); ++i) {
@@ -791,9 +791,9 @@ class SinkOp {
         }
         mutable_vertex->mutable_label()->set_id(label);
         // label must be set
-        auto column_ptrs = column_ptrs[label];
-        for (auto j = 0; j < column_ptrs.size(); ++j) {
-          auto& column_ptr = column_ptrs[j];
+        auto cur_column_ptr = column_ptrs[label];
+        for (auto j = 0; j < cur_column_ptr.size(); ++j) {
+          auto& column_ptr = cur_column_ptr[j];
           // Only set non-none properties.
           if (column_ptr) {
             auto new_prop = mutable_vertex->add_properties();
@@ -823,9 +823,9 @@ class SinkOp {
           }
           mutable_vertex->mutable_label()->set_id(label);
           // label must be set
-          auto column_ptrs = column_ptrs[label];
-          for (auto j = 0; j < column_ptrs.size(); ++j) {
-            auto& column_ptr = column_ptrs[j];
+          auto cur_column_ptr = column_ptrs[label];
+          for (auto j = 0; j < cur_column_ptr.size(); ++j) {
+            auto& column_ptr = cur_column_ptr[j];
             // Only set non-none properties.
             if (column_ptr) {
               auto new_prop = mutable_vertex->add_properties();
diff --git a/flex/engines/hqps_db/core/params.h b/flex/engines/hqps_db/core/params.h
index 3c2777401ba9..b6bd8136ed5e 100644
--- a/flex/engines/hqps_db/core/params.h
+++ b/flex/engines/hqps_db/core/params.h
@@ -239,10 +239,10 @@ struct LengthKey {
 };
 
 struct LabelKey {
-  using label_data_type = int32_t;
+  using label_data_type = uint8_t;
   int32_t label_id;
   LabelKey() = default;
-  LabelKey(int32_t id) : label_id(id) {}
+  LabelKey(label_data_type id) : label_id(id) {}
 };
 
 template <typename T>
@@ -442,6 +442,22 @@ struct EdgeExpandOpt {
   Filter<EDGE_FILTER_FUNC, SELECTOR...> edge_filter_;
 };
 
+// EdgeExpand to vertices with multiple edge triplet.
+// The edge triplet are in the form of <src_label, dst_label, edge_label>
+template <typename LabelT, typename EDGE_FILTER_FUNC>
+struct EdgeExpandVMultiTripletOpt {
+  EdgeExpandVMultiTripletOpt(
+      Direction dir, std::vector<std::array<LabelT, 3>>&& edge_label_triplets,
+      EDGE_FILTER_FUNC&& edge_filter)
+      : direction_(dir),
+        edge_label_triplets_(std::move(edge_label_triplets)),
+        edge_filter_(std::move(edge_filter)) {}
+
+  Direction direction_;
+  std::vector<std::array<LabelT, 3>> edge_label_triplets_;
+  EDGE_FILTER_FUNC edge_filter_;
+};
+
 template <typename LabelT, typename EDGE_FILTER_FUNC, typename Selectors,
           typename... T>
 struct EdgeExpandEOpt;
@@ -581,6 +597,23 @@ auto make_edge_expand_multie_opt(
       Filter<TruePredicate>());
 }
 
+// Expand with multiple edge triplet pairs. resulting vertices, prop names and
+// prop types are not needed.
+template <typename LabelT, typename FILTER_T>
+auto make_edge_expand_multiv_opt(
+    Direction dir, std::vector<std::array<LabelT, 3>>&& edge_label_triplets,
+    FILTER_T&& func) {
+  return EdgeExpandVMultiTripletOpt<LabelT, FILTER_T>(
+      dir, std::move(edge_label_triplets), std::move(func));
+}
+
+template <typename LabelT>
+auto make_edge_expand_multiv_opt(
+    Direction dir, std::vector<std::array<LabelT, 3>>&& edge_label_triplets) {
+  return EdgeExpandVMultiTripletOpt<LabelT, Filter<TruePredicate>>(
+      dir, std::move(edge_label_triplets), Filter<TruePredicate>());
+}
+
 // For edge expand with multiple labels.
 template <typename LabelT, size_t num_labels, typename EDGE_FILTER_FUNC>
 struct EdgeExpandOptMultiLabel {
@@ -738,11 +771,78 @@ struct PathExpandOptImpl {
   ResultOpt result_opt_;  // Get all vertices on Path or only ending vertices.
 };
 
+// Path expand with only one edge label, but possible multiple dst labels.
+template <typename LabelT, size_t num_labels, typename EDGE_FILTER_T,
+          size_t get_v_num_labels, typename VERTEX_FILTER_T,
+          typename UNTIL_CONDITION, typename... T>
+struct PathExpandMultiDstOptImpl {
+  PathExpandMultiDstOptImpl(
+      EdgeExpandOptMultiLabel<LabelT, num_labels, EDGE_FILTER_T>&&
+          edge_expand_opt,
+      GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...>&& get_v_opt,
+      Range&& range, UNTIL_CONDITION&& until_condition,
+      PathOpt path_opt = PathOpt::Arbitrary,
+      ResultOpt result_opt = ResultOpt::EndV)
+      : edge_expand_opt_(std::move(edge_expand_opt)),
+        get_v_opt_(std::move(get_v_opt)),
+        range_(std::move(range)),
+        until_condition_(std::move(until_condition)),
+        path_opt_(path_opt),
+        result_opt_(result_opt) {}
+
+  EdgeExpandOptMultiLabel<LabelT, num_labels, EDGE_FILTER_T> edge_expand_opt_;
+  GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...> get_v_opt_;
+  Range range_;  // Range for result vertices, default is [0,INT_MAX)
+  UNTIL_CONDITION until_condition_;
+  PathOpt path_opt_;      // Single path or not.
+  ResultOpt result_opt_;  // Get all vertices on Path or only ending vertices.
+};
+
+// Path expandv with multiple edge triplets. The src vertices can also contain
+// many labels
+template <typename LabelT, typename EDGE_FILTER_T, size_t get_v_num_labels,
+          typename VERTEX_FILTER_T, typename UNTIL_CONDITION, typename... T>
+struct PathExpandVMultiTripletOptImpl {
+  PathExpandVMultiTripletOptImpl(
+      EdgeExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T>&& edge_expand_opt,
+      GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...>&& get_v_opt,
+      Range&& range, UNTIL_CONDITION&& until_condition,
+      PathOpt path_opt = PathOpt::Arbitrary,
+      ResultOpt result_opt = ResultOpt::EndV)
+      : edge_expand_opt_(std::move(edge_expand_opt)),
+        get_v_opt_(std::move(get_v_opt)),
+        range_(std::move(range)),
+        until_condition_(std::move(until_condition)),
+        path_opt_(path_opt),
+        result_opt_(result_opt) {}
+
+  EdgeExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T> edge_expand_opt_;
+  GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...> get_v_opt_;
+  Range range_;  // Range for result vertices, default is [0,INT_MAX)
+  UNTIL_CONDITION until_condition_;
+  PathOpt path_opt_;      // Single path or not.
+  ResultOpt result_opt_;  // Get all vertices on Path or only ending vertices.
+};
+
 template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T,
           typename... T>
 using PathExpandVOpt = PathExpandOptImpl<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T,
                                          Filter<TruePredicate>, T...>;
 
+template <typename LabelT, size_t num_labels, typename EDGE_FILTER_T,
+          size_t get_v_num_labels, typename VERTEX_FILTER_T, typename... T>
+using PathExpandVMultiDstOpt =
+    PathExpandMultiDstOptImpl<LabelT, num_labels, EDGE_FILTER_T,
+                              get_v_num_labels, VERTEX_FILTER_T,
+                              Filter<TruePredicate>, T...>;
+
+template <typename LabelT, typename EDGE_FILTER_T, size_t get_v_num_labels,
+          typename VERTEX_FILTER_T, typename... T>
+using PathExpandVMultiTripletOpt =
+    PathExpandVMultiTripletOptImpl<LabelT, EDGE_FILTER_T, get_v_num_labels,
+                                   VERTEX_FILTER_T, Filter<TruePredicate>,
+                                   T...>;
+
 template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T>
 using PathExpandPOpt = PathExpandOptImpl<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T,
                                          Filter<TruePredicate>>;
@@ -754,6 +854,7 @@ using ShortestPathOpt =
     PathExpandOptImpl<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T, UNTIL_CONDITION,
                       T...>;
 
+// make path expand opt with only one dst label.
 template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T,
           typename... T>
 auto make_path_expandv_opt(
@@ -766,6 +867,35 @@ auto make_path_expandv_opt(
       Filter<TruePredicate>(), path_opt, result_opt);
 }
 
+// make path expand opt with only one edge label, but multiple dst labels.
+template <typename LabelT, size_t num_labels, typename EDGE_FILTER_T,
+          size_t get_v_num_labels, typename VERTEX_FILTER_T, typename... T>
+auto make_path_expandv_opt(
+    EdgeExpandOptMultiLabel<LabelT, num_labels, EDGE_FILTER_T>&&
+        edge_expand_opt,
+    GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...>&& get_v_opt,
+    Range&& range, PathOpt path_opt = PathOpt::Arbitrary,
+    ResultOpt result_opt = ResultOpt::EndV) {
+  return PathExpandVMultiDstOpt<LabelT, num_labels, EDGE_FILTER_T,
+                                get_v_num_labels, VERTEX_FILTER_T, T...>(
+      std::move(edge_expand_opt), std::move(get_v_opt), std::move(range),
+      Filter<TruePredicate>(), path_opt, result_opt);
+}
+
+// make path expand opt with multiple edge label triplet.
+template <typename LabelT, typename EDGE_FILTER_T, size_t get_v_num_labels,
+          typename VERTEX_FILTER_T, typename... T>
+auto make_path_expandv_opt(
+    EdgeExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T>&& edge_expand_opt,
+    GetVOpt<LabelT, get_v_num_labels, VERTEX_FILTER_T, T...>&& get_v_opt,
+    Range&& range, PathOpt path_opt = PathOpt::Arbitrary,
+    ResultOpt result_opt = ResultOpt::EndV) {
+  return PathExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T, get_v_num_labels,
+                                    VERTEX_FILTER_T, T...>(
+      std::move(edge_expand_opt), std::move(get_v_opt), std::move(range),
+      Filter<TruePredicate>(), path_opt, result_opt);
+}
+
 template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T>
 auto make_path_expandp_opt(
     EdgeExpandOpt<LabelT, EDGE_FILTER_T>&& edge_expand_opt,
diff --git a/flex/engines/hqps_db/core/sync_engine.h b/flex/engines/hqps_db/core/sync_engine.h
index 96dc908b7918..a5abc9944232 100644
--- a/flex/engines/hqps_db/core/sync_engine.h
+++ b/flex/engines/hqps_db/core/sync_engine.h
@@ -95,7 +95,8 @@ class SyncEngine : public BaseEngine {
             typename... SELECTOR,
             typename std::enable_if<(append_opt == AppendOpt::Persist &&
                                      num_labels != 2)>::type* = nullptr,
-            typename COL_T = GeneralVertexSet<vertex_id_t, label_id_t>>
+            typename COL_T =
+                GeneralVertexSet<vertex_id_t, label_id_t, grape::EmptyType>>
   static Context<COL_T, 0, 0, grape::EmptyType> ScanVertex(
       const GRAPH_INTERFACE& graph,
       std::array<label_id_t, num_labels>&& v_labels,
@@ -111,7 +112,8 @@ class SyncEngine : public BaseEngine {
             typename... SELECTOR,
             typename std::enable_if<(append_opt == AppendOpt::Temp &&
                                      num_labels != 2)>::type* = nullptr,
-            typename COL_T = GeneralVertexSet<vertex_id_t, label_id_t>>
+            typename COL_T =
+                GeneralVertexSet<vertex_id_t, label_id_t, grape::EmptyType>>
   static Context<COL_T, 0, 0, grape::EmptyType> ScanVertex(
       const GRAPH_INTERFACE& graph,
       std::array<label_id_t, num_labels>&& v_labels,
@@ -185,11 +187,11 @@ class SyncEngine : public BaseEngine {
     return Context<COL_T, -1, 0, grape::EmptyType>(std::move(v_set_tuple));
   }
 
-  template <AppendOpt append_opt, typename OID_T, typename LabelT,
-            size_t num_labels,
-            typename std::enable_if<(append_opt == AppendOpt::Persist)>::type* =
-                nullptr,
-            typename COL_T = GeneralVertexSet<vertex_id_t, LabelT>>
+  template <
+      AppendOpt append_opt, typename OID_T, typename LabelT, size_t num_labels,
+      typename std::enable_if<(append_opt == AppendOpt::Persist)>::type* =
+          nullptr,
+      typename COL_T = GeneralVertexSet<vertex_id_t, LabelT, grape::EmptyType>>
   static Context<COL_T, 0, 0, grape::EmptyType> ScanVertexWithOid(
       const GRAPH_INTERFACE& graph, std::array<LabelT, num_labels> v_labels,
       OID_T oid) {
@@ -203,7 +205,7 @@ class SyncEngine : public BaseEngine {
   template <
       AppendOpt append_opt, typename LabelT, size_t num_labels,
       typename std::enable_if<(append_opt == AppendOpt::Temp)>::type* = nullptr,
-      typename COL_T = GeneralVertexSet<vertex_id_t, LabelT>>
+      typename COL_T = GeneralVertexSet<vertex_id_t, LabelT, grape::EmptyType>>
   static Context<COL_T, -1, 0, grape::EmptyType> ScanVertexWithOid(
       const GRAPH_INTERFACE& graph, std::array<LabelT, num_labels> v_labels,
       int64_t oid) {
@@ -421,6 +423,57 @@ class SyncEngine : public BaseEngine {
     // old context will be abandon here.
   }
 
+  template <AppendOpt opt, int alias_to_use, typename VERTEX_FILTER_T,
+            typename CTX_HEAD_T, int cur_alias, int base_tag,
+            typename... CTX_PREV, typename LabelT, size_t num_labels,
+            typename EDGE_FILTER_T, size_t get_v_num_labels, typename... T>
+  static auto PathExpandV(
+      const GRAPH_INTERFACE& graph,
+      Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
+      PathExpandVMultiDstOpt<LabelT, num_labels, EDGE_FILTER_T,
+                             get_v_num_labels, VERTEX_FILTER_T, T...>&&
+          path_expand_opt) {
+    if (path_expand_opt.path_opt_ != PathOpt::Arbitrary) {
+      LOG(FATAL) << "Only support Arbitrary path now";
+    }
+    if (path_expand_opt.result_opt_ != ResultOpt::EndV) {
+      LOG(FATAL) << "Only support EndV now";
+    }
+    auto& select_node = gs::Get<alias_to_use>(ctx);
+    auto pair = PathExpand<GRAPH_INTERFACE>::PathExpandV(
+        graph, select_node, std::move(path_expand_opt));
+
+    // create new context node, update offsets.
+    return ctx.template AddNode<opt>(std::move(pair.first),
+                                     std::move(pair.second), alias_to_use);
+    // old context will be abandon here.
+  }
+
+  template <AppendOpt opt, int alias_to_use, typename VERTEX_FILTER_T,
+            typename CTX_HEAD_T, int cur_alias, int base_tag,
+            typename... CTX_PREV, typename LabelT, typename EDGE_FILTER_T,
+            size_t get_v_num_labels, typename... T>
+  static auto PathExpandV(
+      const GRAPH_INTERFACE& graph,
+      Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
+      PathExpandVMultiTripletOpt<LabelT, EDGE_FILTER_T, get_v_num_labels,
+                                 VERTEX_FILTER_T, T...>&& path_expand_opt) {
+    if (path_expand_opt.path_opt_ != PathOpt::Arbitrary) {
+      LOG(FATAL) << "Only support Arbitrary path now";
+    }
+    if (path_expand_opt.result_opt_ != ResultOpt::EndV) {
+      LOG(FATAL) << "Only support EndV now";
+    }
+    auto& select_node = gs::Get<alias_to_use>(ctx);
+    auto pair = PathExpand<GRAPH_INTERFACE>::PathExpandVMultiTriplet(
+        graph, select_node, std::move(path_expand_opt));
+
+    // create new context node, update offsets.
+    return ctx.template AddNode<opt>(std::move(pair.first),
+                                     std::move(pair.second), alias_to_use);
+    // old context will be abandon here.
+  }
+
   /// Expand to Path
   template <AppendOpt opt, int alias_to_use, typename CTX_HEAD_T, int cur_alias,
             int base_tag, typename... CTX_PREV, typename LabelT,
diff --git a/flex/engines/hqps_db/core/utils/keyed.h b/flex/engines/hqps_db/core/utils/keyed.h
index 4a895bb6af24..78f9539a5285 100644
--- a/flex/engines/hqps_db/core/utils/keyed.h
+++ b/flex/engines/hqps_db/core/utils/keyed.h
@@ -101,7 +101,7 @@ struct KeyedT<KeyedRowVertexSetImpl<LabelT, KEY_T, VID_T, SET_T...>,
   using keyed_set_t = KeyedRowVertexSetImpl<LabelT, VID_T, SET_T...>;
   // // The builder type.
   using keyed_builder_t =
-    KeyedRowVertexSetBuilder<LabelT, KEY_T, VID_T, SET_T...>;
+      KeyedRowVertexSetBuilder<LabelT, KEY_T, VID_T, SET_T...>;
   using unkeyed_builder_t =
       typename KeyedRowVertexSetImpl<LabelT, KEY_T, VID_T, SET_T...>::builder_t;
   static keyed_builder_t create_keyed_builder(
@@ -221,16 +221,16 @@ struct KeyedAggT<GI, TwoLabelVertexSet<VID_T, LabelT, T...>,
 };
 
 // general vertex set to_count
-template <typename GI, typename VID_T, typename LabelT, typename PropT,
-          int tag_id>
-struct KeyedAggT<GI, GeneralVertexSet<VID_T, LabelT>, AggFunc::COUNT,
+template <typename GI, typename VID_T, typename LabelT, typename... SET_T,
+          typename PropT, int tag_id>
+struct KeyedAggT<GI, GeneralVertexSet<VID_T, LabelT, SET_T...>, AggFunc::COUNT,
                  std::tuple<PropT>, std::integer_sequence<int32_t, tag_id>> {
   using agg_res_t = Collection<size_t>;
   // build a counter array.
   using aggregate_res_builder_t = CountBuilder<tag_id>;
 
   static aggregate_res_builder_t create_agg_builder(
-      const GeneralVertexSet<VID_T, LabelT>& set, const GI& graph,
+      const GeneralVertexSet<VID_T, LabelT, SET_T...>& set, const GI& graph,
       std::tuple<PropertySelector<PropT>>& selectors) {
     return CountBuilder<tag_id>();
   }
diff --git a/flex/engines/hqps_db/core/utils/props.h b/flex/engines/hqps_db/core/utils/props.h
index 311bc39292d5..5ef63881b8a5 100644
--- a/flex/engines/hqps_db/core/utils/props.h
+++ b/flex/engines/hqps_db/core/utils/props.h
@@ -51,7 +51,7 @@ class RowVertexSetImpl;
 template <typename VID_T, typename LabelT, typename... T>
 class TwoLabelVertexSetImpl;
 
-template <typename VID_T, typename LabelT>
+template <typename VID_T, typename LabelT, typename... SET_T>
 class GeneralVertexSet;
 
 template <typename GI, typename PropTupleT>
@@ -653,10 +653,10 @@ static auto create_prop_getter_impl(
 
 // get for common properties for keyed_row_vertex_set
 template <int tag_id, typename prop_t, typename GRAPH_INTERFACE,
-          typename LabelT, typename VID_T>
-static auto create_prop_getter_impl(const GeneralVertexSet<VID_T, LabelT>& set,
-                                    const GRAPH_INTERFACE& graph,
-                                    const std::string& prop_name) {
+          typename LabelT, typename VID_T, typename... SET_T>
+static auto create_prop_getter_impl(
+    const GeneralVertexSet<VID_T, LabelT, SET_T...>& set,
+    const GRAPH_INTERFACE& graph, const std::string& prop_name) {
   using prop_getter_t =
       typename GRAPH_INTERFACE::template single_prop_getter_t<prop_t>;
   // const std::array<std::string, 2>& labels = set.GetLabels();
@@ -669,7 +669,7 @@ static auto create_prop_getter_impl(const GeneralVertexSet<VID_T, LabelT>& set,
 
   return GeneralVertexSetPropGetter<
       tag_id, prop_getter_t,
-      typename GeneralVertexSet<VID_T, LabelT>::index_ele_tuple_t>(
+      typename GeneralVertexSet<VID_T, LabelT, SET_T...>::index_ele_tuple_t>(
       std::move(prop_getters), set.GetBitsets());
 }
 
diff --git a/flex/engines/hqps_db/database/adj_list.h b/flex/engines/hqps_db/database/adj_list.h
index f9b786d05c7c..0e8c6fa0a194 100644
--- a/flex/engines/hqps_db/database/adj_list.h
+++ b/flex/engines/hqps_db/database/adj_list.h
@@ -618,6 +618,7 @@ class Nbr {
 class NbrList {
  public:
   NbrList(const Nbr* b, const Nbr* e) : begin_(b), end_(e) {}
+  NbrList() : begin_(nullptr), end_(nullptr) {}
   ~NbrList() = default;
 
   const Nbr* begin() const { return begin_; }
diff --git a/flex/engines/hqps_db/database/mutable_csr_interface.h b/flex/engines/hqps_db/database/mutable_csr_interface.h
index dbccf1cba117..782d306d3169 100644
--- a/flex/engines/hqps_db/database/mutable_csr_interface.h
+++ b/flex/engines/hqps_db/database/mutable_csr_interface.h
@@ -696,38 +696,39 @@ class MutableCSRInterface {
       const label_id_t& edge_label_id, const std::vector<vertex_id_t>& vids,
       const std::string& direction_str, size_t limit) const {
     mutable_csr_graph_impl::NbrListArray ret;
-
+    ret.resize(vids.size());
     if (direction_str == "out" || direction_str == "Out" ||
         direction_str == "OUT") {
       auto csr = db_session_.graph().get_oe_csr(src_label_id, dst_label_id,
                                                 edge_label_id);
-      ret.resize(vids.size());
-      for (size_t i = 0; i < vids.size(); ++i) {
-        auto v = vids[i];
-        auto iter = csr->edge_iter(v);
-        auto& vec = ret.get_vector(i);
-        while (iter->is_valid()) {
-          vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
-          iter->next();
+      if (csr) {
+        for (size_t i = 0; i < vids.size(); ++i) {
+          auto v = vids[i];
+          auto iter = csr->edge_iter(v);
+          auto& vec = ret.get_vector(i);
+          while (iter->is_valid()) {
+            vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
+            iter->next();
+          }
         }
       }
     } else if (direction_str == "in" || direction_str == "In" ||
                direction_str == "IN") {
       auto csr = db_session_.graph().get_ie_csr(dst_label_id, src_label_id,
                                                 edge_label_id);
-      ret.resize(vids.size());
-      for (size_t i = 0; i < vids.size(); ++i) {
-        auto v = vids[i];
-        auto iter = csr->edge_iter(v);
-        auto& vec = ret.get_vector(i);
-        while (iter->is_valid()) {
-          vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
-          iter->next();
+      if (csr) {
+        for (size_t i = 0; i < vids.size(); ++i) {
+          auto v = vids[i];
+          auto iter = csr->edge_iter(v);
+          auto& vec = ret.get_vector(i);
+          while (iter->is_valid()) {
+            vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
+            iter->next();
+          }
         }
       }
     } else if (direction_str == "both" || direction_str == "Both" ||
                direction_str == "BOTH") {
-      ret.resize(vids.size());
       auto ocsr = db_session_.graph().get_oe_csr(src_label_id, dst_label_id,
                                                  edge_label_id);
       auto icsr = db_session_.graph().get_ie_csr(dst_label_id, src_label_id,
@@ -735,15 +736,19 @@ class MutableCSRInterface {
       for (size_t i = 0; i < vids.size(); ++i) {
         auto v = vids[i];
         auto& vec = ret.get_vector(i);
-        auto iter = ocsr->edge_iter(v);
-        while (iter->is_valid()) {
-          vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
-          iter->next();
+        if (ocsr) {
+          auto iter = ocsr->edge_iter(v);
+          while (iter->is_valid()) {
+            vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
+            iter->next();
+          }
         }
-        iter = icsr->edge_iter(v);
-        while (iter->is_valid()) {
-          vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
-          iter->next();
+        if (icsr) {
+          auto iter = icsr->edge_iter(v);
+          while (iter->is_valid()) {
+            vec.push_back(mutable_csr_graph_impl::Nbr(iter->get_neighbor()));
+            iter->next();
+          }
         }
       }
     } else {
diff --git a/flex/engines/hqps_db/structures/multi_edge_set/flat_edge_set.h b/flex/engines/hqps_db/structures/multi_edge_set/flat_edge_set.h
index e736d43c231b..8c3b2b0c0e3c 100644
--- a/flex/engines/hqps_db/structures/multi_edge_set/flat_edge_set.h
+++ b/flex/engines/hqps_db/structures/multi_edge_set/flat_edge_set.h
@@ -265,9 +265,10 @@ class FlatEdgeSet {
   size_t Size() const { return vec_.size(); }
 
   template <size_t num_labels, typename FILTER_T>
-  std::pair<GeneralVertexSet<VID_T, LabelT>, std::vector<size_t>> GetVertices(
-      VOpt v_opt, const std::array<LabelT, num_labels>& labels,
-      FILTER_T& expr) const {
+  std::pair<GeneralVertexSet<VID_T, LabelT, grape::EmptyType>,
+            std::vector<size_t>>
+  GetVertices(VOpt v_opt, const std::array<LabelT, num_labels>& labels,
+              FILTER_T& expr) const {
     std::vector<offset_t> offsets;
     std::vector<VID_T> vids;
     offsets.reserve(Size());
diff --git a/flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h b/flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h
index 6aca59310b6e..ee4abad40c84 100644
--- a/flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h
+++ b/flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h
@@ -265,7 +265,8 @@ class UnTypedEdgeSet {
   const std::vector<uint8_t>& GetLabelIndices() const { return label_indices_; }
 
   template <size_t num_labels>
-  std::pair<GeneralVertexSet<vid_t, label_t>, std::vector<offset_t>>
+  std::pair<GeneralVertexSet<vid_t, label_t, grape::EmptyType>,
+            std::vector<offset_t>>
   GetVertices(const GetVOpt<label_t, num_labels, Filter<TruePredicate>>&
                   get_v_opt) const {
     auto v_opt = get_v_opt.v_opt_;
@@ -289,7 +290,8 @@ class UnTypedEdgeSet {
     }
   }
 
-  std::pair<GeneralVertexSet<vid_t, label_t>, std::vector<offset_t>>
+  std::pair<GeneralVertexSet<vid_t, label_t, grape::EmptyType>,
+            std::vector<offset_t>>
   getSrcVertices(const std::vector<label_t>& req_labels) const {
     std::vector<vid_t> ret;
     std::vector<offset_t> offset;
@@ -328,7 +330,8 @@ class UnTypedEdgeSet {
     return std::make_pair(std::move(general_set), std::move(offset));
   }
 
-  std::pair<GeneralVertexSet<vid_t, label_t>, std::vector<offset_t>>
+  std::pair<GeneralVertexSet<vid_t, label_t, grape::EmptyType>,
+            std::vector<offset_t>>
   getDstVertices(const std::vector<label_t>& req_labels) const {
     std::vector<vid_t> ret;
     std::vector<offset_t> offset;
diff --git a/flex/engines/hqps_db/structures/multi_vertex_set/general_vertex_set.h b/flex/engines/hqps_db/structures/multi_vertex_set/general_vertex_set.h
index df086f248744..30933d3f9205 100644
--- a/flex/engines/hqps_db/structures/multi_vertex_set/general_vertex_set.h
+++ b/flex/engines/hqps_db/structures/multi_vertex_set/general_vertex_set.h
@@ -110,6 +110,91 @@ auto general_project_vertices_impl(
                          std::move(offset));
 }
 
+template <typename VID_T, typename DATA_TUPLE, typename LabelT, typename EXPR,
+          typename PROP_GETTER, size_t filter_num_labels>
+auto general_project_vertices_impl(
+    const std::vector<VID_T>& old_vec,
+    const std::vector<DATA_TUPLE>& old_data_vec,
+    const std::vector<grape::Bitset>& old_bit_sets,
+    const std::vector<LabelT>& old_labels,
+    const std::array<LabelT, filter_num_labels>& filter_labels,
+    const EXPR& expr, const std::vector<PROP_GETTER>& prop_getters) {
+  std::vector<VID_T> res_vec;
+  std::vector<DATA_TUPLE> res_data_vec;
+  CHECK(old_bit_sets.size() == old_labels.size());
+  CHECK(prop_getters.size() == old_labels.size());
+  auto old_num_labels = old_bit_sets.size();
+  std::vector<grape::Bitset> res_bitsets(old_num_labels);
+  // reserve enough size for bitset.
+  for (auto i = 0; i < old_num_labels; ++i) {
+    res_bitsets[i].init(old_vec.size());
+  }
+  std::vector<size_t> select_label_id;
+  if constexpr (filter_num_labels == 0) {
+    for (auto i = 0; i < old_labels.size(); ++i) {
+      select_label_id.emplace_back(i);
+    }
+  } else {
+    std::unordered_set<LabelT> set;
+    for (auto l : filter_labels) {
+      set.insert(l);
+    }
+    for (auto i = 0; i < old_labels.size(); ++i) {
+      if (set.find(old_labels[i]) != set.end()) {
+        select_label_id.emplace_back(i);
+      }
+    }
+  }
+  VLOG(10) << "selected label ids: " << gs::to_string(select_label_id)
+           << ", out of size: " << old_labels.size();
+  std::vector<offset_t> offset;
+
+  offset.emplace_back(0);
+  for (auto i = 0; i < old_vec.size(); ++i) {
+    for (auto label_id : select_label_id) {
+      if (old_bit_sets[label_id].get_bit(i)) {
+        auto eles = prop_getters[label_id].get_view(old_vec[i]);
+        if constexpr (PROP_GETTER::prop_num == 0) {
+          if (expr()) {
+            res_bitsets[label_id].set_bit(res_vec.size());
+            res_vec.push_back(old_vec[i]);
+            res_data_vec.push_back(old_data_vec[i]);
+            break;
+          }
+        } else {
+          if (expr(std::get<0>(eles))) {
+            res_bitsets[label_id].set_bit(res_vec.size());
+            res_vec.push_back(old_vec[i]);
+            res_data_vec.push_back(old_data_vec[i]);
+            break;
+          }
+        }
+      }
+    }
+    offset.emplace_back(res_vec.size());
+  }
+  for (auto i = 0; i < res_vec.size(); ++i) {
+    bool flag = false;
+    for (auto j = 0; j < old_num_labels; ++j) {
+      flag |= res_bitsets[j].get_bit(i);
+    }
+    CHECK(flag) << "check fail at ind: " << i;
+  }
+  // resize bitset.
+  for (auto i = 0; i < old_num_labels; ++i) {
+    res_bitsets[i].resize(res_vec.size());
+  }
+  for (auto i = 0; i < res_vec.size(); ++i) {
+    bool flag = false;
+    for (auto j = 0; j < old_num_labels; ++j) {
+      flag |= res_bitsets[j].get_bit(i);
+    }
+    CHECK(flag) << "check fail at ind: " << i;
+  }
+  return std::make_tuple(std::move(res_vec), std::move(res_data_vec),
+                         std::move(res_bitsets), std::move(offset));
+}
+
 template <typename VID_T, typename LabelT, size_t filter_num_labels>
 auto general_project_vertices_no_expr_impl(
     const std::vector<VID_T>& old_vec,
@@ -176,6 +261,76 @@ auto general_project_vertices_no_expr_impl(
                          std::move(offset));
 }
 
+template <typename VID_T, typename DATA_TUPLE, typename LabelT,
+          size_t filter_num_labels>
+auto general_project_vertices_no_expr_impl(
+    const std::vector<VID_T>& old_vec,
+    const std::vector<DATA_TUPLE>& old_data_vec,
+    const std::vector<grape::Bitset>& old_bit_sets,
+    const std::vector<LabelT>& old_labels,
+    const std::array<LabelT, filter_num_labels>& filter_labels) {
+  auto old_num_labels = old_bit_sets.size();
+  std::vector<VID_T> res_vec;
+  std::vector<DATA_TUPLE> res_data_vec;
+  std::vector<grape::Bitset> res_bitsets(old_num_labels);
+  // reserve enough size for bitset.
+  for (auto i = 0; i < old_num_labels; ++i) {
+    res_bitsets[i].init(old_vec.size());
+  }
+  std::vector<size_t> select_label_id;
+  if constexpr (filter_num_labels == 0) {
+    for (auto i = 0; i < old_labels.size(); ++i) {
+      select_label_id.emplace_back(i);
+    }
+  } else {
+    std::unordered_set<LabelT> set;
+    for (auto l : filter_labels) {
+      set.insert(l);
+    }
+    for (auto i = 0; i < old_labels.size(); ++i) {
+      if (set.find(old_labels[i]) != set.end()) {
+        select_label_id.emplace_back(i);
+      }
+    }
+  }
+  VLOG(10) << "selected label ids: " << gs::to_string(select_label_id)
+           << ", out of size: " << old_labels.size();
+  std::vector<offset_t> offset;
+
+  offset.emplace_back(0);
+  for (auto i = 0; i < old_vec.size(); ++i) {
+    for (auto label_id : select_label_id) {
+      if (old_bit_sets[label_id].get_bit(i)) {
+        res_bitsets[label_id].set_bit(res_vec.size());
+        res_vec.push_back(old_vec[i]);
+        res_data_vec.push_back(old_data_vec[i]);
+        break;
+      }
+    }
+    offset.emplace_back(res_vec.size());
+  }
+  for (auto i = 0; i < res_vec.size(); ++i) {
+    bool flag = false;
+    for (auto j = 0; j < old_num_labels; ++j) {
+      flag |= res_bitsets[j].get_bit(i);
+    }
+    CHECK(flag) << "check fail at ind: " << i;
+  }
+  // resize bitset.
+  for (auto i = 0; i < old_num_labels; ++i) {
+    res_bitsets[i].resize(res_vec.size());
+  }
+  for (auto i = 0; i < res_vec.size(); ++i) {
+    bool flag = false;
+    for (auto j = 0; j < old_num_labels; ++j) {
+      flag |= res_bitsets[j].get_bit(i);
+    }
+    CHECK(flag) << "check fail at ind: " << i;
+  }
+  return std::make_tuple(std::move(res_vec), std::move(res_data_vec),
+                         std::move(res_bitsets), std::move(offset));
+}
+
 template <int tag_id, int res_id, int... Is, typename lid_t>
 auto general_project_with_repeat_array_impl(
     const KeyAlias<tag_id, res_id, Is...>& key_alias,
@@ -194,6 +349,41 @@ auto general_project_with_repeat_array_impl(
   return res_vec;
 }
 
+template <size_t col_ind, typename... index_ele_tuple_t, typename DATA_TUPLE,
+          typename lid_t>
+auto generalSetFlatImpl(
+    std::vector<std::tuple<index_ele_tuple_t...>>& index_ele_tuples,
+    const std::vector<lid_t>& origin_vids,
+    const std::vector<DATA_TUPLE>& origin_data,
+    const std::vector<grape::Bitset>& origin_bitsets) {
+  size_t dst_size = index_ele_tuples.size();
+  std::vector<lid_t> res_vids;
+  std::vector<DATA_TUPLE> res_data_vec;
+  std::vector<grape::Bitset> res_bitsets(origin_bitsets.size());
+  res_vids.reserve(dst_size);
+  res_data_vec.reserve(dst_size);
+  for (auto i = 0; i < origin_bitsets.size(); ++i) {
+    res_bitsets[i].init(dst_size);
+  }
+  for (auto ele : index_ele_tuples) {
+    auto& cur = std::get<col_ind>(ele);
+    //(ind, vid)
+    auto ind = std::get<0>(cur);
+    CHECK(ind < origin_vids.size());
+
+    for (auto i = 0; i < origin_bitsets.size(); ++i) {
+      if (origin_bitsets[i].get_bit(ind)) {
+        res_bitsets[i].set_bit(res_vids.size());
+        break;
+      }
+    }
+    res_vids.emplace_back(origin_vids[ind]);
+    res_data_vec.emplace_back(origin_data[ind]);
+  }
+  return std::make_tuple(std::move(res_vids), std::move(res_data_vec),
+                         std::move(res_bitsets));
+}
+
 template <size_t col_ind, typename... index_ele_tuple_t, typename lid_t>
 auto generalSetFlatImpl(
     std::vector<std::tuple<index_ele_tuple_t...>>& index_ele_tuples,
@@ -223,11 +413,75 @@ auto generalSetFlatImpl(
   return std::make_pair(std::move(res_vids), std::move(res_bitsets));
 }
 
-template <typename VID_T>
+template <typename VID_T, typename... T>
 class GeneralVertexSetIter {
  public:
   using lid_t = VID_T;
-  using self_type_t = GeneralVertexSetIter<VID_T>;
+  using self_type_t = GeneralVertexSetIter<VID_T, T...>;
+  using index_ele_tuple_t = std::tuple<size_t, VID_T, std::tuple<T...>>;
+  using data_tuple_t = std::tuple<VID_T, std::tuple<T...>>;
+
+  GeneralVertexSetIter(const std::vector<VID_T>& vec,
+                       const std::vector<std::tuple<T...>>& data_vec,
+                       const std::vector<std::string>& prop_names,
+                       const std::vector<grape::Bitset>& bitsets, size_t ind)
+      : vec_(vec),
+        data_vec_(data_vec),
+        prop_names_(prop_names),
+        bitsets_(bitsets),
+        ind_(ind) {}
+
+  lid_t GetElement() const { return vec_[ind_]; }
+
+  data_tuple_t GetData() const { return vec_[ind_]; }
+
+  index_ele_tuple_t GetIndexElement() const {
+    return std::make_tuple(ind_, vec_[ind_], data_vec_[ind_]);
+  }
+
+  lid_t GetVertex() const { return vec_[ind_]; }
+
+  inline const self_type_t& operator++() {
+    ++ind_;
+    return *this;
+  }
+
+  inline self_type_t operator++(int) {
+    self_type_t ret(*this);
+    ++ind_;
+    return ret;
+  }
+
+  // We may never compare to other kind of iterators
+  inline bool operator==(const self_type_t& rhs) const {
+    return ind_ == rhs.ind_;
+  }
+
+  inline bool operator!=(const self_type_t& rhs) const {
+    return ind_ != rhs.ind_;
+  }
+
+  inline bool operator<(const self_type_t& rhs) const {
+    return ind_ < rhs.ind_;
+  }
+
+  inline const self_type_t& operator*() const { return *this; }
+
+  inline const self_type_t* operator->() const { return this; }
+
+ private:
+  const std::vector<VID_T>& vec_;
+  const std::vector<std::tuple<T...>>& data_vec_;
+  const std::vector<std::string>& prop_names_;
+  const std::vector<grape::Bitset>& bitsets_;
+  size_t ind_;
+};
+
+template <typename VID_T>
+class GeneralVertexSetIter<VID_T, grape::EmptyType> {
+ public:
+  using lid_t = VID_T;
+  using self_type_t = GeneralVertexSetIter<VID_T, grape::EmptyType>;
   using index_ele_tuple_t = std::tuple<size_t, VID_T>;
   using data_tuple_t = std::tuple<VID_T>;
 
@@ -279,18 +533,14 @@ class GeneralVertexSetIter {
   size_t ind_;
 };
 
-/// @brief GeneralVertexSet are designed for the case we need to store multiple
-/// label vertex in a mixed manner
-/// @tparam VID_T
-/// @tparam LabelT
-template <typename VID_T, typename LabelT>
+template <typename VID_T, typename LabelT, typename... T>
 class GeneralVertexSet {
  public:
   using lid_t = VID_T;
-  using self_type_t = GeneralVertexSet<VID_T, LabelT>;
-  using iterator = GeneralVertexSetIter<VID_T>;
-  using index_ele_tuple_t = std::tuple<size_t, VID_T>;
-  using data_tuple_t = std::tuple<VID_T>;
+  using self_type_t = GeneralVertexSet<VID_T, LabelT, T...>;
+  using iterator = GeneralVertexSetIter<VID_T, T...>;
+  using index_ele_tuple_t = std::tuple<size_t, VID_T, std::tuple<T...>>;
+  using data_tuple_t = std::tuple<VID_T, std::tuple<T...>>;
   using flat_t = self_type_t;
   using EntityValueType = VID_T;
 
@@ -299,10 +549,18 @@ class GeneralVertexSet {
   static constexpr bool is_general_set = true;
   static constexpr bool is_collection = false;
   static constexpr bool is_multi_label = false;
-  GeneralVertexSet(std::vector<VID_T>&& vec, std::vector<LabelT>&& label_names,
+  static constexpr bool is_row_vertex_set = false;
+  GeneralVertexSet(std::vector<VID_T>&& vec,
+                   std::vector<std::tuple<T...>>&& data_vec,
+                   std::vector<std::string>&& prop_names,
+                   std::vector<LabelT>&& label_names,
                    std::vector<grape::Bitset>&& bitsets)
-      : vec_(std::move(vec)), label_names_(std::move(label_names)) {
+      : vec_(std::move(vec)),
+        data_vec_(std::move(data_vec)),
+        prop_names_(std::move(prop_names)),
+        label_names_(std::move(label_names)) {
     CHECK(label_names_.size() == bitsets.size());
+    CHECK(vec_.size() == data_vec_.size());
     bitsets_.resize(bitsets.size());
     for (auto i = 0; i < bitsets.size(); ++i) {
       bitsets_[i].swap(bitsets[i]);
@@ -316,6 +574,8 @@ class GeneralVertexSet {
 
   GeneralVertexSet(GeneralVertexSet&& other)
       : vec_(std::move(other.vec_)),
+        data_vec_(std::move(other.data_vec_)),
+        prop_names_(std::move(other.prop_names_)),
         label_names_(std::move(other.label_names_)) {
     bitsets_.resize(other.bitsets_.size());
     for (auto i = 0; i < bitsets_.size(); ++i) {
@@ -328,7 +588,10 @@ class GeneralVertexSet {
   }
 
   GeneralVertexSet(const GeneralVertexSet& other)
-      : vec_(other.vec_), label_names_(other.label_names_) {
+      : vec_(other.vec_),
+        data_vec_(other.data_vec_),
+        prop_names_(other.prop_names_),
+        label_names_(other.label_names_) {
     bitsets_.resize(other.bitsets_.size());
     for (auto i = 0; i < bitsets_.size(); ++i) {
       bitsets_[i].copy(other.bitsets_[i]);
@@ -339,9 +602,13 @@ class GeneralVertexSet {
     }
   }
 
-  iterator begin() const { return iterator(vec_, bitsets_, 0); }
+  iterator begin() const {
+    return iterator(vec_, data_vec_, prop_names_, bitsets_, 0);
+  }
 
-  iterator end() const { return iterator(vec_, bitsets_, vec_.size()); }
+  iterator end() const {
+    return iterator(vec_, data_vec_, prop_names_, bitsets_, vec_.size());
+  }
 
   template <typename EXPRESSION, size_t num_labels, typename PROP_GETTER,
             typename RES_SET_T = self_type_t,
@@ -352,13 +619,16 @@ class GeneralVertexSet {
     // TODO: vector-based cols should be able to be selected with
     // certain rows.
 
-    auto tuple = general_project_vertices_impl(
-        vec_, bitsets_, label_names_, filter_labels, exprs, prop_getter);
+    auto tuple =
+        general_project_vertices_impl(vec_, data_vec_, bitsets_, label_names_,
+                                      filter_labels, exprs, prop_getter);
     auto copied_label_names(label_names_);
-    auto set = self_type_t(std::move(std::get<0>(tuple)),
-                           std::move(copied_label_names),
-                           std::move(std::get<1>(tuple)));
-    return std::make_pair(std::move(set), std::move(std::get<2>(tuple)));
+    auto copied_prop_names(prop_names_);
+    auto set = self_type_t(
+        std::move(std::get<0>(tuple)), std::move(std::get<1>(tuple)),
+        std::move(copied_prop_names), std::move(copied_label_names),
+        std::move(std::get<2>(tuple)));
+    return std::make_pair(std::move(set), std::move(std::get<3>(tuple)));
   }
 
   // project without expression.
@@ -369,12 +639,14 @@ class GeneralVertexSet {
     // certain rows.
 
     auto tuple = general_project_vertices_no_expr_impl(
-        vec_, bitsets_, label_names_, filter_labels);
+        vec_, data_vec_, bitsets_, label_names_, filter_labels);
     auto copied_label_names(label_names_);
-    auto set = self_type_t(std::move(std::get<0>(tuple)),
-                           std::move(copied_label_names),
-                           std::move(std::get<1>(tuple)));
-    return std::make_pair(std::move(set), std::move(std::get<2>(tuple)));
+    auto copied_prop_names(prop_names_);
+    auto set = self_type_t(
+        std::move(std::get<0>(tuple)), std::move(std::get<1>(tuple)),
+        std::move(copied_prop_names), std::move(copied_label_names),
+        std::move(std::get<2>(tuple)));
+    return std::make_pair(std::move(set), std::move(std::get<3>(tuple)));
   }
 
   const std::vector<LabelT>& GetLabels() const { return label_names_; }
@@ -449,13 +721,16 @@ class GeneralVertexSet {
   // subset with indices.
   void SubSetWithIndices(std::vector<size_t>& indices) {
     std::vector<VID_T> res_vec;
+    std::vector<std::tuple<T...>> res_data_vec;
     std::vector<grape::Bitset> res_bitsets(bitsets_.size());
     for (auto& i : res_bitsets) {
       i.init(indices.size());
     }
     res_vec.reserve(indices.size());
+    res_data_vec.reserve(indices.size());
     for (auto i : indices) {
       res_vec.emplace_back(vec_[i]);
+      res_data_vec.emplace_back(data_vec_[i]);
       for (auto j = 0; j < bitsets_.size(); ++j) {
         if (bitsets_[j].get_bit(i)) {
           res_bitsets[j].set_bit(i);
@@ -464,6 +739,7 @@ class GeneralVertexSet {
       }
     }
     vec_.swap(res_vec);
+    data_vec_.swap(res_data_vec);
     for (auto i = 0; i < bitsets_.size(); ++i) {
       bitsets_[i].swap(res_bitsets[i]);
     }
@@ -474,6 +750,7 @@ class GeneralVertexSet {
   self_type_t ProjectWithRepeatArray(const std::vector<size_t>& repeat_array,
                                      KeyAlias<tag_id, Fs>& key_alias) const {
     std::vector<lid_t> next_vids;
+    std::vector<std::tuple<T...>> next_data_vec;
     size_t next_size = 0;
     for (auto i = 0; i < repeat_array.size(); ++i) {
       next_size += repeat_array[i];
@@ -482,6 +759,7 @@ class GeneralVertexSet {
              << " Project self, next size: " << next_size;
 
     next_vids.reserve(next_size);
+    next_data_vec.reserve(next_size);
     std::vector<grape::Bitset> next_sets(bitsets_.size());
     for (auto& i : next_sets) {
       i.init(next_size);
@@ -500,12 +778,15 @@ class GeneralVertexSet {
         // VLOG(10) << "Project: " << vids_[i];
         next_sets[ind].set_bit(next_vids.size());
         next_vids.push_back(vec_[i]);
+        next_data_vec.push_back(data_vec_[i]);
       }
     }
 
     auto copied_label_names(label_names_);
-    return self_type_t(std::move(next_vids), std::move(copied_label_names),
-                       std::move(next_sets));
+    auto copied_prop_names(prop_names_);
+    return self_type_t(std::move(next_vids), std::move(next_data_vec),
+                       std::move(copied_prop_names),
+                       std::move(copied_label_names), std::move(next_sets));
   }
 
   void Repeat(std::vector<offset_t>& cur_offset,
@@ -514,6 +795,7 @@ class GeneralVertexSet {
     CHECK(cur_offset.back() == vec_.back())
         << "neq : " << cur_offset.back() << ", " << vec_.back();
     std::vector<lid_t> res_vec;
+    std::vector<std::tuple<T...>> res_data_vec;
     std::vector<grape::Bitset> res_bitsets(bitsets_.size());
     size_t total_cnt = repeat_vec.back();
     VLOG(10) << "Repeat current vertices num: " << vec_.size() << ", to "
@@ -525,12 +807,14 @@ class GeneralVertexSet {
       auto label_indices = GenerateLabelIndices();
       size_t cur_ind = 0;
       res_vec.reserve(repeat_vec.back());
+      res_data_vec.reserve(repeat_vec.back());
       for (auto i = 0; i + 1 < cur_offset.size(); ++i) {
         auto times_to_repeat = repeat_vec[i + 1] - repeat_vec[i];
         auto num_ele = cur_offset[i + 1] - cur_offset[i];
         for (auto j = 0; j < times_to_repeat; ++j) {
           for (auto k = cur_offset[i]; k < cur_offset[i + 1]; ++k) {
             res_vec.emplace_back(vec_[k]);
+            res_data_vec.emplace_back(data_vec_[k]);
             CHECK(label_indices[k] < res_bitsets.size());
             res_bitsets[label_indices[k]].set_bit(cur_ind++);
           }
@@ -539,6 +823,7 @@ class GeneralVertexSet {
       CHECK(cur_ind == repeat_vec.back());
     }
     vec_.swap(res_vec);
+    data_vec_.swap(res_data_vec);
     bitsets_.swap(res_bitsets);
     VLOG(10) << "Finish Repeat general vertex";
   }
@@ -549,21 +834,367 @@ class GeneralVertexSet {
     static_assert(col_ind <
                   std::tuple_size_v<std::tuple<index_ele_tuple_t_...>>);
     auto res_vids_and_data_tuples =
-        generalSetFlatImpl<col_ind>(index_ele_tuple, vec_, bitsets_);
+        generalSetFlatImpl<col_ind>(index_ele_tuple, vec_, data_vec_, bitsets_);
     auto labels_copied(label_names_);
-    return self_type_t(std::move(res_vids_and_data_tuples.first),
-                       std::move(labels_copied),
-                       std::move(res_vids_and_data_tuples.second));
+    auto prop_names_copied(prop_names_);
+    return self_type_t(std::move(std::get<0>(res_vids_and_data_tuples)),
+                       std::move(std::get<1>(res_vids_and_data_tuples)),
+                       std::move(prop_names_copied), std::move(labels_copied),
+                       std::move(std::get<2>(res_vids_and_data_tuples)));
   }
 
-  template <size_t Is, typename... PropT>
+  template <size_t Is, size_t MyIs, typename... PropT>
   void fillBuiltinPropsImpl(std::vector<std::tuple<PropT...>>& tuples,
-                            std::string& prop_name,
+                            const std::string& prop_name,
                             std::vector<offset_t>& repeat_array) const {
-    if constexpr (std::is_same_v<std::tuple_element_t<Is, std::tuple<PropT...>>,
-                                 Dist>) {
-      if (prop_name == "dist") {
-        LOG(FATAL) << "Not supported";
+    using cur_prop = std::tuple_element_t<Is, std::tuple<PropT...>>;
+    using my_prop = std::tuple_element_t<MyIs, std::tuple<T...>>;
+    if constexpr (std::is_same_v<cur_prop, my_prop>) {
+      CHECK(MyIs < prop_names_.size());
+      if (prop_name == prop_names_[MyIs]) {
+        VLOG(10) << "Found builtin property " << prop_name;
+        CHECK(repeat_array.size() == data_vec_.size());
+        size_t ind = 0;
+        for (auto i = 0; i < repeat_array.size(); ++i) {
+          for (auto j = 0; j < repeat_array[i]; ++j) {
+            std::get<Is>(tuples[ind]) = std::get<0>(data_vec_[i]);
+            ind += 1;
+          }
+        }
+      }
+    }
+  }
+
+  template <size_t Is, typename... PropT, size_t... MyIs>
+  void fillBuiltinPropsImpl(std::vector<std::tuple<PropT...>>& tuples,
+                            std::string& prop_name,
+                            std::vector<offset_t>& repeat_array,
+                            std::index_sequence<MyIs...>) const {
+    (fillBuiltinPropsImpl<Is, MyIs>(tuples, prop_name, repeat_array), ...);
+  }
+
+  template <typename... PropT, size_t... Is>
+  void fillBuiltinPropsImpl(std::vector<std::tuple<PropT...>>& tuples,
+                            PropNameArray<PropT...>& prop_names,
+                            std::vector<offset_t>& repeat_array,
+                            std::index_sequence<Is...>) const {
+    (fillBuiltinPropsImpl<Is, PropT...>(tuples, std::get<Is>(prop_names),
+                                        repeat_array,
+                                        std::index_sequence<sizeof...(T)>()),
+     ...);
+  }
+
+  template <typename... PropT>
+  void fillBuiltinProps(std::vector<std::tuple<PropT...>>& tuples,
+                        PropNameArray<PropT...>& prop_names,
+                        std::vector<offset_t>& repeat_array) const {
+    fillBuiltinPropsImpl(tuples, prop_names, repeat_array,
+                         std::make_index_sequence<sizeof...(PropT)>());
+  }
+
+  // No repeat array is not provided
+  template <typename... PropT>
+  void fillBuiltinProps(std::vector<std::tuple<PropT...>>& tuples,
+                        PropNameArray<PropT...>& prop_names) {
+    // TO be implemented.
+  }
+
+  size_t Size() const { return vec_.size(); }
+
+ private:
+  std::vector<VID_T> vec_;
+  std::vector<std::tuple<T...>> data_vec_;
+  std::vector<LabelT> label_names_;
+  std::vector<std::string> prop_names_;
+  std::vector<grape::Bitset> bitsets_;
+};
+
+/// @brief GeneralVertexSet are designed for the case we need to store multiple
+/// label vertex in a mixed manner
+/// @tparam VID_T
+/// @tparam LabelT
+template <typename VID_T, typename LabelT>
+class GeneralVertexSet<VID_T, LabelT, grape::EmptyType> {
+ public:
+  using lid_t = VID_T;
+  using self_type_t = GeneralVertexSet<VID_T, LabelT, grape::EmptyType>;
+  using iterator = GeneralVertexSetIter<VID_T, grape::EmptyType>;
+  using index_ele_tuple_t = std::tuple<size_t, VID_T>;
+  using data_tuple_t = std::tuple<VID_T>;
+  using flat_t = self_type_t;
+  using EntityValueType = VID_T;
+
+  static constexpr bool is_vertex_set = true;
+  static constexpr bool is_two_label_set = false;
+  static constexpr bool is_general_set = true;
+  static constexpr bool is_collection = false;
+  static constexpr bool is_multi_label = false;
+  static constexpr bool is_row_vertex_set = false;
+  GeneralVertexSet(std::vector<VID_T>&& vec, std::vector<LabelT>&& label_names,
+                   std::vector<grape::Bitset>&& bitsets)
+      : vec_(std::move(vec)), label_names_(std::move(label_names)) {
+    CHECK(label_names_.size() == bitsets.size());
+    bitsets_.resize(bitsets.size());
+    for (auto i = 0; i < bitsets.size(); ++i) {
+      bitsets_[i].swap(bitsets[i]);
+    }
+    if (bitsets_.size() > 0) {
+      VLOG(10) << "[GeneralVertexSet], size: " << vec_.size()
+               << ", bitset size: " << bitsets_[0].cardinality()
+               << ", ind_ele_t: " << demangle(index_ele_tuple_t());
+    }
+  }
+
+  GeneralVertexSet(GeneralVertexSet&& other)
+      : vec_(std::move(other.vec_)),
+        label_names_(std::move(other.label_names_)) {
+    bitsets_.resize(other.bitsets_.size());
+    for (auto i = 0; i < bitsets_.size(); ++i) {
+      bitsets_[i].swap(other.bitsets_[i]);
+    }
+    if (bitsets_.size() > 0) {
+      VLOG(10) << "[GeneralVertexSet], size: " << vec_.size()
+               << ", bitset size: " << bitsets_[0].cardinality();
+    }
+  }
+
+  GeneralVertexSet(const GeneralVertexSet& other)
+      : vec_(other.vec_), label_names_(other.label_names_) {
+    bitsets_.resize(other.bitsets_.size());
+    for (auto i = 0; i < bitsets_.size(); ++i) {
+      bitsets_[i].copy(other.bitsets_[i]);
+    }
+    if (bitsets_.size() > 0) {
+      VLOG(10) << "[GeneralVertexSet], size: " << vec_.size()
+               << ", bitset size: " << bitsets_[0].cardinality();
+    }
+  }
+
+  iterator begin() const { return iterator(vec_, bitsets_, 0); }
+
+  iterator end() const { return iterator(vec_, bitsets_, vec_.size()); }
+
+  template <typename EXPRESSION, size_t num_labels, typename PROP_GETTER,
+            typename RES_SET_T = self_type_t,
+            typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  RES_T project_vertices(std::array<LabelT, num_labels>& filter_labels,
+                         EXPRESSION& exprs,
+                         std::vector<PROP_GETTER>& prop_getter) const {
+    // TODO: vector-based cols should be able to be selected with
+    // certain rows.
+
+    auto tuple = general_project_vertices_impl(
+        vec_, bitsets_, label_names_, filter_labels, exprs, prop_getter);
+    auto copied_label_names(label_names_);
+    auto set = self_type_t(std::move(std::get<0>(tuple)),
+                           std::move(copied_label_names),
+                           std::move(std::get<1>(tuple)));
+    return std::make_pair(std::move(set), std::move(std::get<2>(tuple)));
+  }
+
+  // project without expression.
+  template <size_t num_labels, typename RES_SET_T = self_type_t,
+            typename RES_T = std::pair<RES_SET_T, std::vector<offset_t>>>
+  RES_T project_vertices(std::array<LabelT, num_labels>& filter_labels) const {
+    // TODO: vector-based cols should be able to be selected with
+    // certain rows.
+
+    auto tuple = general_project_vertices_no_expr_impl(
+        vec_, bitsets_, label_names_, filter_labels);
+    auto copied_label_names(label_names_);
+    auto set = self_type_t(std::move(std::get<0>(tuple)),
+                           std::move(copied_label_names),
+                           std::move(std::get<1>(tuple)));
+    return std::make_pair(std::move(set), std::move(std::get<2>(tuple)));
+  }
+
+  const std::vector<LabelT>& GetLabels() const { return label_names_; }
+
+  LabelT GetLabel(size_t i) const { return label_names_[i]; }
+
+  const std::vector<LabelKey> GetLabelVec() const {
+    std::vector<LabelKey> res;
+    // fill res with vertex labels
+    for (auto i = 0; i < vec_.size(); ++i) {
+      for (auto j = 0; j < bitsets_.size(); ++j) {
+        if (bitsets_[j].get_bit(i)) {
+          res.emplace_back(label_names_[j]);
+          break;
+        }
+      }
+    }
+    return res;
+  }
+
+  // generate label indices.
+  std::vector<uint8_t> GenerateLabelIndices() const {
+    std::vector<uint8_t> label_indices;
+    label_indices.resize(vec_.size(), 255);
+    for (auto i = 0; i < bitsets_.size(); ++i) {
+      for (auto j = 0; j < bitsets_[i].cardinality(); ++j) {
+        if (bitsets_[i].get_bit(j)) {
+          CHECK(label_indices[j] == 255);
+          label_indices[j] = i;
+        }
+      }
+    }
+    return label_indices;
+  }
+
+  const std::vector<grape::Bitset>& GetBitsets() const { return bitsets_; }
+
+  const std::vector<VID_T>& GetVertices() const { return vec_; }
+
+  std::pair<std::vector<VID_T>, std::vector<int32_t>> GetVerticesWithLabel(
+      label_t label_id) const {
+    // find label_id in label_names_
+    auto it = std::find(label_names_.begin(), label_names_.end(), label_id);
+    if (it == label_names_.end()) {
+      return std::make_pair(std::vector<VID_T>(), std::vector<int32_t>());
+    } else {
+      auto ind = std::distance(label_names_.begin(), it);
+      return GetVerticesWithIndex(ind);
+    }
+  }
+
+  std::pair<std::vector<VID_T>, std::vector<int32_t>> GetVerticesWithIndex(
+      size_t ind) const {
+    CHECK(ind < bitsets_.size());
+    std::vector<VID_T> res;
+    std::vector<int32_t> active_ind;
+    size_t cnt = bitsets_[ind].count();
+    res.reserve(cnt);
+    active_ind.reserve(cnt);
+    for (auto i = 0; i < bitsets_[ind].cardinality(); ++i) {
+      if (bitsets_[ind].get_bit(i)) {
+        res.push_back(vec_[i]);
+        active_ind.push_back(i);
+      }
+    }
+    VLOG(10) << "Got vertices of tag: " << ind
+             << ", res vertices: " << res.size()
+             << ", active_ind: " << active_ind.size();
+    return std::make_pair(std::move(res), std::move(active_ind));
+  }
+
+  // subset with indices.
+  void SubSetWithIndices(std::vector<size_t>& indices) {
+    std::vector<VID_T> res_vec;
+    std::vector<grape::Bitset> res_bitsets(bitsets_.size());
+    for (auto& i : res_bitsets) {
+      i.init(indices.size());
+    }
+    res_vec.reserve(indices.size());
+    for (auto i : indices) {
+      res_vec.emplace_back(vec_[i]);
+      for (auto j = 0; j < bitsets_.size(); ++j) {
+        if (bitsets_[j].get_bit(i)) {
+          res_bitsets[j].set_bit(i);
+          break;
+        }
+      }
+    }
+    vec_.swap(res_vec);
+    for (auto i = 0; i < bitsets_.size(); ++i) {
+      bitsets_[i].swap(res_bitsets[i]);
+    }
+  }
+
+  template <int tag_id, int Fs,
+            typename std::enable_if<Fs == -1>::type* = nullptr>
+  self_type_t ProjectWithRepeatArray(const std::vector<size_t>& repeat_array,
+                                     KeyAlias<tag_id, Fs>& key_alias) const {
+    std::vector<lid_t> next_vids;
+    size_t next_size = 0;
+    for (auto i = 0; i < repeat_array.size(); ++i) {
+      next_size += repeat_array[i];
+    }
+    VLOG(10) << "[GeneralVertexSet] size: " << Size()
+             << " Project self, next size: " << next_size;
+
+    next_vids.reserve(next_size);
+    std::vector<grape::Bitset> next_sets(bitsets_.size());
+    for (auto& i : next_sets) {
+      i.init(next_size);
+    }
+    VLOG(10) << "after init";
+    for (auto i = 0; i < repeat_array.size(); ++i) {
+      size_t ind = 0;
+      while (ind < bitsets_.size()) {
+        if (bitsets_[ind].get_bit(i)) {
+          break;
+        }
+        ind += 1;
+      }
+      CHECK(ind < bitsets_.size());
+      for (auto j = 0; j < repeat_array[i]; ++j) {
+        // VLOG(10) << "Project: " << vids_[i];
+        next_sets[ind].set_bit(next_vids.size());
+        next_vids.push_back(vec_[i]);
+      }
+    }
+
+    auto copied_label_names(label_names_);
+    return self_type_t(std::move(next_vids), std::move(copied_label_names),
+                       std::move(next_sets));
+  }
+
+  void Repeat(std::vector<offset_t>& cur_offset,
+              std::vector<offset_t>& repeat_vec) {
+    CHECK(cur_offset.size() == repeat_vec.size());
+    CHECK(cur_offset.back() == vec_.back())
+        << "neq : " << cur_offset.back() << ", " << vec_.back();
+    std::vector<lid_t> res_vec;
+    std::vector<grape::Bitset> res_bitsets(bitsets_.size());
+    size_t total_cnt = repeat_vec.back();
+    VLOG(10) << "Repeat current vertices num: " << vec_.size() << ", to "
+             << total_cnt;
+    for (auto i = 0; i < res_bitsets.size(); ++i) {
+      res_bitsets[i].init(total_cnt);
+    }
+    {
+      auto label_indices = GenerateLabelIndices();
+      size_t cur_ind = 0;
+      res_vec.reserve(repeat_vec.back());
+      for (auto i = 0; i + 1 < cur_offset.size(); ++i) {
+        auto times_to_repeat = repeat_vec[i + 1] - repeat_vec[i];
+        auto num_ele = cur_offset[i + 1] - cur_offset[i];
+        for (auto j = 0; j < times_to_repeat; ++j) {
+          for (auto k = cur_offset[i]; k < cur_offset[i + 1]; ++k) {
+            res_vec.emplace_back(vec_[k]);
+            CHECK(label_indices[k] < res_bitsets.size());
+            res_bitsets[label_indices[k]].set_bit(cur_ind++);
+          }
+        }
+      }
+      CHECK(cur_ind == repeat_vec.back());
+    }
+    vec_.swap(res_vec);
+    bitsets_.swap(res_bitsets);
+    VLOG(10) << "Finish Repeat general vertex";
+  }
+
+  // Usually after sort.
+  template <size_t col_ind, typename... index_ele_tuple_t_>
+  flat_t Flat(std::vector<std::tuple<index_ele_tuple_t_...>>& index_ele_tuple) {
+    static_assert(col_ind <
+                  std::tuple_size_v<std::tuple<index_ele_tuple_t_...>>);
+    auto res_vids_and_data_tuples =
+        generalSetFlatImpl<col_ind>(index_ele_tuple, vec_, bitsets_);
+    auto labels_copied(label_names_);
+    return self_type_t(std::move(res_vids_and_data_tuples.first),
+                       std::move(labels_copied),
+                       std::move(res_vids_and_data_tuples.second));
+  }
+
+  template <size_t Is, typename... PropT>
+  void fillBuiltinPropsImpl(std::vector<std::tuple<PropT...>>& tuples,
+                            std::string& prop_name,
+                            std::vector<offset_t>& repeat_array) const {
+    if constexpr (std::is_same_v<std::tuple_element_t<Is, std::tuple<PropT...>>,
+                                 Dist>) {
+      if (prop_name == "dist") {
+        LOG(FATAL) << "Not supported";
       }
     }
   }
@@ -607,8 +1238,8 @@ template <typename VID_T, typename LabelT>
 auto make_general_set(std::vector<VID_T>&& vec,
                       std::vector<LabelT>&& label_names,
                       std::vector<grape::Bitset>&& bitsets) {
-  return GeneralVertexSet<VID_T, LabelT>(std::move(vec), std::move(label_names),
-                                         std::move(bitsets));
+  return GeneralVertexSet<VID_T, LabelT, grape::EmptyType>(
+      std::move(vec), std::move(label_names), std::move(bitsets));
 }
 
 template <typename VID_T, typename LabelT>
@@ -616,10 +1247,21 @@ auto make_general_set(std::vector<VID_T>&& vec,
                       const std::vector<LabelT>& label_names,
                       std::vector<grape::Bitset>&& bitsets) {
   auto copied_label_names(label_names);
-  return GeneralVertexSet<VID_T, LabelT>(
+  return GeneralVertexSet<VID_T, LabelT, grape::EmptyType>(
       std::move(vec), std::move(copied_label_names), std::move(bitsets));
 }
 
+template <typename VID_T, typename LabelT, typename... T>
+auto make_general_set(std::vector<VID_T>&& vec,
+                      std::vector<std::tuple<T...>>&& data_vec,
+                      std::vector<std::string>&& prop_names,
+                      std::vector<LabelT>&& label_names,
+                      std::vector<grape::Bitset>&& bitsets) {
+  return GeneralVertexSet<VID_T, LabelT, T...>(
+      std::move(vec), std::move(data_vec), std::move(prop_names),
+      std::move(label_names), std::move(bitsets));
+}
+
 static std::vector<std::vector<int32_t>> bitsets_to_vids_inds(
     const std::vector<grape::Bitset>& bitset) {
   auto num_labels = bitset.size();
@@ -648,11 +1290,12 @@ static std::vector<std::vector<int32_t>> bitsets_to_vids_inds(
   return res;
 }
 
-template <typename... T, typename GRAPH_INTERFACE, typename LabelT>
+template <typename... T, typename GRAPH_INTERFACE, typename LabelT,
+          typename... SET_T>
 static auto get_property_tuple_general(
     const GRAPH_INTERFACE& graph,
-    const GeneralVertexSet<typename GRAPH_INTERFACE::vertex_id_t, LabelT>&
-        general_set,
+    const GeneralVertexSet<typename GRAPH_INTERFACE::vertex_id_t, LabelT,
+                           SET_T...>& general_set,
     const std::array<std::string, sizeof...(T)>& prop_names) {
   auto label_vec = general_set.GetLabels();
   auto vids_inds = bitsets_to_vids_inds(general_set.GetBitsets());
@@ -663,11 +1306,12 @@ static auto get_property_tuple_general(
   return data_tuples;
 }
 
-template <typename... T, typename GRAPH_INTERFACE, typename LabelT>
+template <typename... T, typename GRAPH_INTERFACE, typename LabelT,
+          typename... SET_T>
 static auto get_property_tuple_general(
     const GRAPH_INTERFACE& graph,
-    const GeneralVertexSet<typename GRAPH_INTERFACE::vertex_id_t, LabelT>&
-        general_set,
+    const GeneralVertexSet<typename GRAPH_INTERFACE::vertex_id_t, LabelT,
+                           SET_T...>& general_set,
     const std::tuple<NamedProperty<T>...>& named_prop) {
   std::array<std::string, sizeof...(T)> prop_names;
   int ind = 0;
diff --git a/flex/engines/hqps_db/structures/multi_vertex_set/keyed_row_vertex_set.h b/flex/engines/hqps_db/structures/multi_vertex_set/keyed_row_vertex_set.h
index 170a10972e41..8002bf4bf083 100644
--- a/flex/engines/hqps_db/structures/multi_vertex_set/keyed_row_vertex_set.h
+++ b/flex/engines/hqps_db/structures/multi_vertex_set/keyed_row_vertex_set.h
@@ -569,7 +569,7 @@ class KeyedRowVertexSetImpl {
 
   LabelT GetLabel() const { return v_label_; }
 
-  const std::vector<LabelKey> GetLabelVec() {
+  std::vector<LabelKey> GetLabelVec() const {
     std::vector<LabelKey> res;
     // fill res with vertex labels
     res.reserve(vids_.size());
@@ -778,7 +778,7 @@ class KeyedRowVertexSetImpl<LabelT, KEY_T, VID_T, grape::EmptyType> {
 
   LabelT GetLabel() const { return v_label_; }
 
-  const std::vector<LabelKey> GetLabelVec() {
+  std::vector<LabelKey> GetLabelVec() const {
     std::vector<LabelKey> res;
     // fill res with vertex labels
     res.reserve(vids_.size());
diff --git a/flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h b/flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h
index 696eda5146c9..6b8f3b93575c 100644
--- a/flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h
+++ b/flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h
@@ -950,7 +950,7 @@ class RowVertexSetImpl {
 
   const LabelT& GetLabel() const { return v_label_; }
 
-  const std::vector<LabelKey> GetLabelVec() {
+  std::vector<LabelKey> GetLabelVec() const {
     std::vector<LabelKey> res;
     // fill res with v_label_
     res.reserve(vids_.size());
@@ -1254,7 +1254,7 @@ class RowVertexSetImpl<LabelT, VID_T, grape::EmptyType> {
 
   const LabelT& GetLabel() const { return v_label_; }
 
-  const std::vector<LabelKey> GetLabelVec() {
+  std::vector<LabelKey> GetLabelVec() const {
     std::vector<LabelKey> res;
     // fill res with v_label_
     res.reserve(vids_.size());
diff --git a/flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h b/flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h
index 8164832c0583..11f989370466 100644
--- a/flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h
+++ b/flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h
@@ -744,7 +744,7 @@ class TwoLabelVertexSetImpl {
 
   const std::array<LabelT, 2>& GetLabels() const { return label_names_; }
 
-  const std::vector<LabelKey> GetLabelVec() {
+  std::vector<LabelKey> GetLabelVec() const {
     std::vector<LabelKey> res;
     // fill with each vertex's label
     for (auto i = 0; i < vec_.size(); ++i) {
@@ -1019,8 +1019,8 @@ class TwoLabelVertexSetImpl<VID_T, LabelT, grape::EmptyType> {
 
   const std::array<LabelT, 2>& GetLabels() const { return label_names_; }
 
-  const std::vector<LabelT> GetLabelVec() {
-    std::vector<LabelT> res;
+  std::vector<LabelKey> GetLabelVec() const {
+    std::vector<LabelKey> res;
     // fill with each vertex's label
     for (auto i = 0; i < vec_.size(); ++i) {
       if (bitset_.get_bit(i)) {
diff --git a/flex/resources/queries/ic/adhoc/simple_match_11.cypher b/flex/resources/queries/ic/adhoc/simple_match_11.cypher
new file mode 100644
index 000000000000..be53017db261
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/simple_match_11.cypher
@@ -0,0 +1,1 @@
+MATCH(a)-[:REPLYOF*0..3]->(b) return a.content, b.content LIMIT 10;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/simple_match_12.cypher b/flex/resources/queries/ic/adhoc/simple_match_12.cypher
new file mode 100644
index 000000000000..1cd3bcf9e4d3
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/simple_match_12.cypher
@@ -0,0 +1,1 @@
+MATCH(a)-[*1..2]->(b) WITH a.id AS aId, b.id AS bId RETURN aId, bId ORDER BY aId ASC, bId ASC LIMIT 10;
\ No newline at end of file
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
index 411f297eecbf..2a5cf5894f9a 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
@@ -151,4 +151,33 @@ public static QueryContext get_simple_match_query_11_test() {
                         "Record<{p: node<4>}>");
         return new QueryContext(query, expected);
     }
+
+    public static QueryContext get_simple_match_query_12_test() {
+        String query =
+                "MATCH(p)<-[:ISLOCATEDIN*1..2]-(a : POST | COMMENT) WITH DISTINCT p, a RETURN p.id"
+                    + " AS placeId, p.name AS placeName, a.id AS postOrCommentId ORDER BY placeId"
+                    + " ASC, postOrCommentId ASC LIMIT 5;";
+        List<String> expected =
+                Arrays.asList(
+                        "Record<{placeId: 0, placeName: \"India\", postOrCommentId: 54780}>",
+                        "Record<{placeId: 0, placeName: \"India\", postOrCommentId: 54971}>",
+                        "Record<{placeId: 0, placeName: \"India\", postOrCommentId: 54972}>",
+                        "Record<{placeId: 0, placeName: \"India\", postOrCommentId: 54973}>",
+                        "Record<{placeId: 0, placeName: \"India\", postOrCommentId: 54974}>");
+        return new QueryContext(query, expected);
+    }
+
+    public static QueryContext get_simple_match_query_13_test() {
+        String query =
+                "MATCH(a)-[*1..2]->(b) WITH a.id AS aId, b.id AS bId RETURN aId, bId ORDER BY aId"
+                        + " ASC, bId ASC LIMIT 5;";
+        List<String> expected =
+                Arrays.asList(
+                        "Record<{aId: 0, bId: 3}>",
+                        "Record<{aId: 0, bId: 59}>",
+                        "Record<{aId: 0, bId: 349}>",
+                        "Record<{aId: 0, bId: 933}>",
+                        "Record<{aId: 0, bId: 1454}>");
+        return new QueryContext(query, expected);
+    }
 }


Test patch:
diff --git a/flex/tests/hqps/match_query.h b/flex/tests/hqps/match_query.h
index 054a55518f6d..b8ee5edbebbf 100644
--- a/flex/tests/hqps/match_query.h
+++ b/flex/tests/hqps/match_query.h
@@ -828,5 +828,123 @@ class MatchQuery13 : public HqpsAppBase<gs::MutableCSRInterface> {
   }
 };
 
+// Auto generated query class definition
+class MatchQuery14 : public HqpsAppBase<gs::MutableCSRInterface> {
+ public:
+  using Engine = SyncEngine<gs::MutableCSRInterface>;
+  using label_id_t = typename gs::MutableCSRInterface::label_id_t;
+  using vertex_id_t = typename gs::MutableCSRInterface::vertex_id_t;
+  // Query function for query class
+  results::CollectiveResults Query(const gs::MutableCSRInterface& graph) const {
+    auto expr0 = gs::make_filter(Query0expr0());
+    auto ctx0 = Engine::template ScanVertex<gs::AppendOpt::Persist>(
+        graph, 2, std::move(expr0));
+
+    auto edge_expand_opt1 = gs::make_edge_expandv_opt(
+        gs::Direction::Out, (label_id_t) 2,
+        std::array<label_id_t, 2>{(label_id_t) 2, (label_id_t) 3});
+
+    auto get_v_opt0 = make_getv_opt(
+        gs::VOpt::Itself,
+        std::array<label_id_t, 2>{(label_id_t) 2, (label_id_t) 3});
+
+    auto path_opt2 = gs::make_path_expandv_opt(
+        std::move(edge_expand_opt1), std::move(get_v_opt0), gs::Range(0, 3));
+    auto ctx1 = Engine::PathExpandV<gs::AppendOpt::Persist, INPUT_COL_ID(0)>(
+        graph, std::move(ctx0), std::move(path_opt2));
+    auto ctx2 = Engine::Project<PROJ_TO_NEW>(
+        graph, std::move(ctx1),
+        std::tuple{gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<int64_t>("id")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(1)>(
+                       gs::PropertySelector<int64_t>("id"))});
+    return Engine::Sink(graph, ctx2, std::array<int32_t, 2>{2, 3});
+  }
+  // Wrapper query function for query class
+  results::CollectiveResults Query(const gs::MutableCSRInterface& graph,
+                                   Decoder& decoder) const override {
+    // decoding params from decoder, and call real query func
+
+    return Query(graph);
+  }
+};
+
+class MatchQuery15 : public HqpsAppBase<gs::MutableCSRInterface> {
+ public:
+  using Engine = SyncEngine<gs::MutableCSRInterface>;
+  using label_id_t = typename gs::MutableCSRInterface::label_id_t;
+  using vertex_id_t = typename gs::MutableCSRInterface::vertex_id_t;
+  // Query function for query class
+  results::CollectiveResults Query(const gs::MutableCSRInterface& graph) const {
+    auto expr0 = gs::make_filter(Query0expr0());
+    auto ctx0 = Engine::template ScanVertex<gs::AppendOpt::Persist>(
+        graph, std::array<label_id_t, 8>{0, 1, 2, 3, 4, 5, 6, 7},
+        std::move(expr0));
+
+    auto edge_expand_opt1 = gs::make_edge_expand_multiv_opt(
+        gs::Direction::Out, std::vector<std::array<label_id_t, 3>>{
+                                std::array<label_id_t, 3>{2, 2, 2},
+                                std::array<label_id_t, 3>{2, 3, 2},
+                                std::array<label_id_t, 3>{1, 7, 6},
+                                std::array<label_id_t, 3>{6, 6, 13},
+                                std::array<label_id_t, 3>{4, 3, 3},
+                                std::array<label_id_t, 3>{2, 0, 7},
+                                std::array<label_id_t, 3>{1, 0, 7},
+                                std::array<label_id_t, 3>{3, 0, 7},
+                                std::array<label_id_t, 3>{5, 0, 7},
+                                std::array<label_id_t, 3>{1, 1, 8},
+                                std::array<label_id_t, 3>{1, 2, 9},
+                                std::array<label_id_t, 3>{1, 3, 9},
+                                std::array<label_id_t, 3>{0, 0, 11},
+                                std::array<label_id_t, 3>{7, 6, 12},
+                                std::array<label_id_t, 3>{2, 1, 0},
+                                std::array<label_id_t, 3>{3, 1, 0},
+                                std::array<label_id_t, 3>{1, 5, 10},
+                                std::array<label_id_t, 3>{4, 1, 4},
+                                std::array<label_id_t, 3>{1, 5, 14},
+                                std::array<label_id_t, 3>{3, 7, 1},
+                                std::array<label_id_t, 3>{4, 1, 5}});
+
+    auto get_v_opt0 = make_getv_opt(
+        gs::VOpt::Itself,
+        std::array<label_id_t, 21>{
+            (label_id_t) 2, (label_id_t) 3, (label_id_t) 7, (label_id_t) 6,
+            (label_id_t) 3, (label_id_t) 0, (label_id_t) 0, (label_id_t) 0,
+            (label_id_t) 0, (label_id_t) 1, (label_id_t) 2, (label_id_t) 3,
+            (label_id_t) 0, (label_id_t) 6, (label_id_t) 1, (label_id_t) 1,
+            (label_id_t) 5, (label_id_t) 1, (label_id_t) 5, (label_id_t) 7,
+            (label_id_t) 1});
+
+    auto path_opt2 = gs::make_path_expandv_opt(
+        std::move(edge_expand_opt1), std::move(get_v_opt0), gs::Range(0, 2));
+    auto ctx1 = Engine::PathExpandV<gs::AppendOpt::Persist, INPUT_COL_ID(0)>(
+        graph, std::move(ctx0), std::move(path_opt2));
+    auto ctx2 = Engine::Project<PROJ_TO_NEW>(
+        graph, std::move(ctx1),
+        std::tuple{gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<int64_t>("id")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(1)>(
+                       gs::PropertySelector<int64_t>("id"))});
+    auto ctx3 = Engine::Project<PROJ_TO_NEW>(
+        graph, std::move(ctx2),
+        std::tuple{gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<grape::EmptyType>("")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(1)>(
+                       gs::PropertySelector<grape::EmptyType>(""))});
+    auto ctx4 = Engine::Sort(
+        graph, std::move(ctx3), gs::Range(0, 10),
+        std::tuple{gs::OrderingPropPair<gs::SortOrder::ASC, 0, int64_t>(""),
+                   gs::OrderingPropPair<gs::SortOrder::ASC, 1, int64_t>("")});
+    return Engine::Sink(graph, ctx4, std::array<int32_t, 2>{2, 3});
+  }
+  // Wrapper query function for query class
+  results::CollectiveResults Query(const gs::MutableCSRInterface& graph,
+                                   Decoder& decoder) const override {
+    // decoding params from decoder, and call real query func
+
+    return Query(graph);
+  }
+};
+
 }  // namespace gs
 #endif  // TESTS_HQPS_MATCH_QUERY_H_
\ No newline at end of file
diff --git a/flex/tests/hqps/query_test.cc b/flex/tests/hqps/query_test.cc
index 8351574c699e..04bfabf5643e 100644
--- a/flex/tests/hqps/query_test.cc
+++ b/flex/tests/hqps/query_test.cc
@@ -227,5 +227,32 @@ int main(int argc, char** argv) {
     LOG(INFO) << "Finish MatchQuery12 test";
   }
 
+  {
+    gs::MatchQuery14 query;
+    std::vector<char> encoder_array;
+    gs::Encoder input_encoder(encoder_array);
+    std::vector<char> output_array;
+    gs::Encoder output(output_array);
+    gs::Decoder input(encoder_array.data(), encoder_array.size());
+
+    gs::MutableCSRInterface graph(sess);
+    query.Query(graph, input);
+    LOG(INFO) << "Finish MatchQuery14 test";
+  }
+
+  {
+    // test PathExpand with multiple edge triplets.
+    gs::MatchQuery15 query;
+    std::vector<char> encoder_array;
+    gs::Encoder input_encoder(encoder_array);
+    std::vector<char> output_array;
+    gs::Encoder output(output_array);
+    gs::Decoder input(encoder_array.data(), encoder_array.size());
+
+    gs::MutableCSRInterface graph(sess);
+    query.Query(graph, input);
+    LOG(INFO) << "Finish MatchQuery14 test";
+  }
+
   LOG(INFO) << "Finish context test.";
 }
\ No newline at end of file
diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/SimpleMatchTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/SimpleMatchTest.java
index c9d95db0ac59..b8f105eadb9a 100644
--- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/SimpleMatchTest.java
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/SimpleMatchTest.java
@@ -114,6 +114,20 @@ public void run_simple_match_11_test() {
         Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
     }
 
+    @Test
+    public void run_simple_match_12_test() {
+        QueryContext testQuery = SimpleMatchQueries.get_simple_match_query_12_test();
+        Result result = session.run(testQuery.getQuery());
+        Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
+    }
+
+    @Test
+    public void run_simple_match_13_test() {
+        QueryContext testQuery = SimpleMatchQueries.get_simple_match_query_13_test();
+        Result result = session.run(testQuery.getQuery());
+        Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
+    }
+
     @AfterClass
     public static void afterClass() {
         if (session != null) {