Repository: alibaba/GraphScope (language: python)

Issue:
 请增加打印多跳路径具体业务号的功能
比如打印  点3-4跳路径的中间所有点，并可以指定业务号和属性来显示，而不是目前单一展示gid。
interactive.execute("g.V().has('trmnl','trmnl_black_flag',1).as('start').both('3..4', 'register').with('PATH_OPT', 'ARBITRARY') \
.with('RESULT_OPT', 'ALL_V').as('allV').select('start','allV'))").one()
结果为：
[{'start': v[2305843009213693975],
  'allV': [v[2305843009213693975],
   v[2886807361144487959],
   v[2305843009213693975],
   v[2886807361144487959]]},
目前打印路径展示的gid没有实际业务意义，希望看到的是起点的终端号，和中间的注册号，没有办法直接展示业务号，需要循环搜索g.V(gid).valueMap()来查询gid对应的业务号，过程有些繁琐。
所以，能否增加打印多跳路径具体业务号的功能，业务号本身就是唯一的。打印效果如
点trmnl，a,b,c,d,e
点code，1,2,3,4
  a-->3-->b-->2-->c-->1        
请支撑直观打印业务号，而不是[v[2305843009213693975],v[2886807361144487959], v[2305843009213693975],v[2886807361144487959]，这样无法看清具体路径，谢谢~


Discussion / hints:
Thanks for opening your first issue here! Be sure to follow the issue template! And a maintainer will get back to you shortly!
Please feel free to contact us on [DingTalk](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingb4b76e0fa9ccd8d516fd0f0acdf19f55&68e71190=0fe34ec0&cbdbhh=qwertyuiop), WeChat account(graphscope) or [Slack](http://slack.graphscope.io/). We are happy to answer your questions responsively.


Fix patch:
diff --git a/.github/workflows/hqps-db-ci.yml b/.github/workflows/hqps-db-ci.yml
index 8f875b64ae36..56f2a0a5fb92 100644
--- a/.github/workflows/hqps-db-ci.yml
+++ b/.github/workflows/hqps-db-ci.yml
@@ -86,7 +86,7 @@ jobs:
         GS_TEST_DIR: ${{ github.workspace }}/gstest
       run: |
         # download dataset
-        git clone -b master --single-branch --depth=1 https://github.com/Graphscope/gstest.git ${GS_TEST_DIR}
+        git clone -b master --single-branch --depth=1 https://github.com/GraphScope/gstest.git ${GS_TEST_DIR}
 
     - name: Sample Query test
       env:
@@ -112,18 +112,18 @@ jobs:
         echo "graph.store: exp" >> /tmp/ir.compiler.properties
         echo "graph.planner.is.on: true" >> /tmp/ir.compiler.properties
         echo "graph.planner.opt: RBO" >> /tmp/ir.compiler.properties
-        echo "graph.planner.rules: FilterMatchRule" >> /tmp/ir.compiler.properties
+        echo "graph.planner.rules: FilterMatchRule,NotMatchToAntiJoinRule" >> /tmp/ir.compiler.properties
 
         cd ${GITHUB_WORKSPACE}/flex/bin
 
-        for i in 2 3 5 6 8 9 11 12;
+        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/ic${i}_adhoc.cypher -w=/tmp/codgen/"
           cmd=${cmd}" -o=/tmp/plugin --ir_conf=/tmp/ir.compiler.properties "
           cmd=${cmd}" --graph_schema_path=${GS_TEST_DIR}/flex/ldbc-sf01-long-date/ldbc_schema_csr_ic.json"
           cmd=${cmd}" --gie_home=${GIE_HOME}"
           echo $cmd
-          eval ${cmd} 
+          eval ${cmd}
         done
 
         for i in 1 2 3 4 5 6 7 8 9;
@@ -143,5 +143,6 @@ jobs:
       run: |
         cd ${GITHUB_WORKSPACE}/flex/tests/hqps/
         export FLEX_DATA_DIR=${GS_TEST_DIR}/flex/ldbc-sf01-long-date
+        export ENGINE_TYPE=hiactor
         bash hqps_cypher_test.sh ${GS_TEST_DIR}
 
diff --git a/flex/codegen/gen_code_from_plan.cc b/flex/codegen/gen_code_from_plan.cc
index 57756b1b81b0..b495d8838747 100644
--- a/flex/codegen/gen_code_from_plan.cc
+++ b/flex/codegen/gen_code_from_plan.cc
@@ -80,7 +80,7 @@ void deserialize_plan_and_gen_hqps(const std::string& input_file_path,
   auto stream = std::istringstream(content_str);
   CHECK(plan_pb.ParseFromArray(content_str.data(), content_str.size()));
   LOG(INFO) << "deserilized plan size : " << plan_pb.ByteSizeLong();
-  LOG(INFO) << "deserilized plan : " << plan_pb.DebugString();
+  VLOG(1) << "deserilized plan : " << plan_pb.DebugString();
   BuildingContext context;
   QueryGenerator<uint8_t> query_generator(context, plan_pb);
   auto res = query_generator.GenerateQuery();
diff --git a/flex/codegen/src/building_context.h b/flex/codegen/src/building_context.h
index 8f11b87ab572..c6f46eda264d 100644
--- a/flex/codegen/src/building_context.h
+++ b/flex/codegen/src/building_context.h
@@ -113,6 +113,10 @@ struct TagIndMapping {
     return tag_ind_2_tag_ids_;
   }
 
+  const std::vector<int32_t>& GetTagId2TagInds() const {
+    return tag_id_2_tag_inds_;
+  }
+
   // convert tag_ind (us) to tag ids
   std::vector<int32_t> tag_ind_2_tag_ids_;
   // convert tag ids(pb) to tag_inds
diff --git a/flex/codegen/src/codegen_utils.h b/flex/codegen/src/codegen_utils.h
index ca62dad52005..d5ea17d3a454 100644
--- a/flex/codegen/src/codegen_utils.h
+++ b/flex/codegen/src/codegen_utils.h
@@ -203,14 +203,56 @@ static codegen::ParamConst variable_to_param_const(const common::Variable& var,
     param_const.var_name = var.property().key().name();
     param_const.type =
         common_data_type_pb_2_data_type(var.node_type().data_type());
-  } else {
-    param_const.var_name = ctx.GetNextVarName();
-    param_const.type = codegen::DataType::kVertexId;
+  } else if (var.has_tag()) {
+    // check is vertex or is edge from node_type
+    if (var.has_node_type()) {
+      auto node_type = var.node_type();
+      param_const.var_name = ctx.GetNextVarName();
+      if (node_type.type_case() == common::IrDataType::kDataType) {
+        param_const.type =
+            common_data_type_pb_2_data_type(node_type.data_type());
+      } else {
+        auto graph_type = node_type.graph_type();
+        if (graph_type.element_opt() ==
+            common::GraphDataType::GraphElementOpt::
+                GraphDataType_GraphElementOpt_VERTEX) {
+          param_const.type = codegen::DataType::kVertexId;
+        } else if (graph_type.element_opt() ==
+                   common::GraphDataType::GraphElementOpt::
+                       GraphDataType_GraphElementOpt_EDGE) {
+          param_const.type = codegen::DataType::kEdgeId;
+        } else {
+          LOG(FATAL) << "Unexpect graph type";
+        }
+      }
+    } else {
+      LOG(FATAL)
+          << "Node type is not given when converting variable to param const";
+    }
   }
 
   return param_const;
 }
 
+static std::string interval_to_str(const common::Extract::Interval& interval) {
+  switch (interval) {
+  case common::Extract::Interval::Extract_Interval_YEAR:
+    return "Interval::YEAR";
+  case common::Extract::Interval::Extract_Interval_MONTH:
+    return "Interval::MONTH";
+  case common::Extract::Interval::Extract_Interval_DAY:
+    return "Interval::DAY";
+  case common::Extract::Interval::Extract_Interval_HOUR:
+    return "Interval::HOUR";
+  case common::Extract::Interval::Extract_Interval_MINUTE:
+    return "Interval::MINUTE";
+  case common::Extract::Interval::Extract_Interval_SECOND:
+    return "Interval::SECOND";
+  default:
+    LOG(FATAL) << "Unexpected interval" << interval;
+  }
+}
+
 }  // namespace gs
 
 #endif  // CODEGEN_SRC_CODEGEN_UTILS_H_
\ No newline at end of file
diff --git a/flex/codegen/src/graph_types.h b/flex/codegen/src/graph_types.h
index feac73237183..d261d69e20e9 100644
--- a/flex/codegen/src/graph_types.h
+++ b/flex/codegen/src/graph_types.h
@@ -38,6 +38,11 @@ enum class DataType {
   kInt32Array = 6,
   kBoolean = 7,
   kVertexId = 8,
+  kEdgeId = 9,
+  kLength = 10,
+  kTime = 11,
+  kDate = 12,
+  kDateTime = 13,
 };
 
 // a parameter const, the real data will be feed at runtime.
@@ -72,14 +77,18 @@ static codegen::DataType common_data_type_pb_2_data_type(
     return codegen::DataType::kInt32Array;
   case common::DataType::BOOLEAN:
     return codegen::DataType::kBoolean;
+  case common::DataType::DATE:
+    return codegen::DataType::kDate;
   default:
     // LOG(FATAL) << "unknown data type";
-    throw std::runtime_error("unknown data type" +
-                             std::to_string(static_cast<int>(data_type)));
+    throw std::runtime_error(
+        "unknown data type when converting common_data_type to inner data "
+        "type:" +
+        std::to_string(static_cast<int>(data_type)));
   }
 }
 
-static std::string common_data_type_pb_2_str(
+static std::string single_common_data_type_pb_2_str(
     const common::DataType& data_type) {
   switch (data_type) {
   case common::DataType::BOOLEAN:
@@ -96,14 +105,34 @@ static std::string common_data_type_pb_2_str(
     return "std::vector<int64_t>";
   case common::DataType::INT32_ARRAY:
     return "std::vector<int32_t>";
+  case common::DataType::DATE:
+    return "Date";
   default:
     // LOG(FATAL) << "unknown data type";
     // return "";
-    throw std::runtime_error("unknown data type" +
-                             std::to_string(static_cast<int>(data_type)));
+    throw std::runtime_error(
+        "unknown data type when convert common data type to string:" +
+        std::to_string(static_cast<int>(data_type)));
   }
 }
 
+static std::string common_data_type_pb_2_str(
+    const std::vector<common::DataType>& data_types) {
+  std::stringstream ss;
+  if (data_types.size() == 1) {
+    return single_common_data_type_pb_2_str(data_types[0]);
+  }
+  ss << "std::tuple<";
+  for (auto i = 0; i < data_types.size(); ++i) {
+    ss << single_common_data_type_pb_2_str(data_types[i]);
+    if (i + 1 < data_types.size()) {
+      ss << ", ";
+    }
+  }
+  ss << ">;";
+  return ss.str();
+}
+
 static std::string arith_to_str(const common::Arithmetic& arith_type) {
   switch (arith_type) {
   case common::Arithmetic::ADD:
@@ -158,10 +187,17 @@ static std::string data_type_2_string(const codegen::DataType& data_type) {
     return "bool";
   case codegen::DataType::kVertexId:
     return VERTEX_ID_T;
+  case codegen::DataType::kLength:
+    return LENGTH_KEY_T;
+  case codegen::DataType::kEdgeId:
+    return EDGE_ID_T;
+  case codegen::DataType::kDate:
+    return "Date";
   default:
     // LOG(FATAL) << "unknown data type" << static_cast<int>(data_type);
-    throw std::runtime_error("unknown data type" +
-                             std::to_string(static_cast<int>(data_type)));
+    throw std::runtime_error(
+        "unknown data type when convert inner data_type to string: " +
+        std::to_string(static_cast<int>(data_type)));
   }
 }
 
@@ -180,7 +216,7 @@ static std::string decode_type_as_str(const codegen::DataType& data_type) {
     return "get_bool()";
   default:
     // LOG(FATAL) << "unknown data type" << static_cast<int>(data_type);
-    throw std::runtime_error("unknown data type" +
+    throw std::runtime_error("unknown data type when decode type as str: " +
                              std::to_string(static_cast<int>(data_type)));
   }
 }
@@ -208,6 +244,23 @@ static std::string data_type_2_rust_string(const codegen::DataType& data_type) {
   }
 }
 
+static common::DataType common_value_2_data_type(const common::Value& value) {
+  switch (value.item_case()) {
+  case common::Value::kI32:
+    return common::DataType::INT32;
+  case common::Value::kI64:
+    return common::DataType::INT64;
+  case common::Value::kBoolean:
+    return common::DataType::BOOLEAN;
+  case common::Value::kF64:
+    return common::DataType::DOUBLE;
+  case common::Value::kStr:
+    return common::DataType::STRING;
+  default:
+    LOG(FATAL) << "unknown value" << value.DebugString();
+  }
+}
+
 static void parse_param_const_from_pb(
     const common::DynamicParam& param_const_pb,
     const common::IrDataType& node_type, codegen::ParamConst& param_cost) {
diff --git a/flex/codegen/src/hqps/hqps_case_when_builder.h b/flex/codegen/src/hqps/hqps_case_when_builder.h
index 7e6cf2cd4f2f..fd4b9ec9884b 100644
--- a/flex/codegen/src/hqps/hqps_case_when_builder.h
+++ b/flex/codegen/src/hqps/hqps_case_when_builder.h
@@ -27,17 +27,25 @@ limitations under the License.
 #include "proto_generated_gie/common.pb.h"
 #include "proto_generated_gie/expr.pb.h"
 
+#include <boost/format.hpp>
+
 namespace gs {
 
+// can be multiple when then exprs
+static constexpr const char* CASE_WHEN_EXPR_TEMPLATE_STR =
+    "if (%1%){\n"
+    "   return %2%;\n"
+    "}\n";
+
+static constexpr const char* ELSE_EXPR_TEMPLATE_STR = "return %1%;\n";
+
 class CaseWhenBuilder : public ExprBuilder {
  public:
   using base_t = ExprBuilder;
   using ret_t = std::tuple<std::string, std::vector<codegen::ParamConst>,
                            std::vector<std::pair<int32_t, std::string>>,
-                           std::string, common::DataType>;
-  CaseWhenBuilder(BuildingContext& ctx)
-      : base_t(ctx),
-        ret_type_(common::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_) {
+                           std::string, std::vector<common::DataType>>;
+  CaseWhenBuilder(BuildingContext& ctx) : base_t(ctx) {
     VLOG(10) << "try to build: " << base_t::class_name_;
   }
 
@@ -63,23 +71,23 @@ class CaseWhenBuilder : public ExprBuilder {
     if (else_exr.operators_size() == 0) {
       throw std::runtime_error("else expression is empty");
     }
+    VLOG(10) << "Building else expr of size: " << else_exr.DebugString();
 
     {
       auto& else_oprs = else_exr.operators();
-      auto expr_code = build_sub_expr(else_oprs);
 
-      std::stringstream ss;
-      ss << "return (";
-      ss << expr_code;
-      ss << ");" << std::endl;
-      else_code_ = ss.str();  // assign to member.
+      auto expr_code = build_sub_expr(else_oprs, true);
+
+      boost::format formater(ELSE_EXPR_TEMPLATE_STR);
+      formater % expr_code;
+      else_code_ = formater.str();
     }
     VLOG(10) << "Finish else expr: " << else_code_;
     return *this;
   }
 
   CaseWhenBuilder& return_type(common::DataType ret_type) {
-    ret_type_ = ret_type;
+    res_data_type_.emplace_back(ret_type);
     return *this;
   }
 
@@ -102,65 +110,57 @@ class CaseWhenBuilder : public ExprBuilder {
     private_filed_str = get_private_filed_str();
 
     boost::format formater(EXPR_BUILDER_TEMPLATE_STR);
-    formater % class_name_ % constructor_param_str % field_init_code_str %
-        func_call_template_typename_str % func_call_params_str %
-        func_call_impl_str % private_filed_str;
+    auto ret_type_str = common_data_type_pb_2_str(res_data_type_);
+    formater % class_name_ % ret_type_str % constructor_param_str %
+        field_init_code_str % func_call_template_typename_str % "auto" %
+        func_call_params_str % func_call_impl_str % private_filed_str;
 
     std::string str = formater.str();
 
     return std::make_tuple(
         class_name_, construct_params_, tag_selectors_, str,
-        common::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_);
+        std::vector{common::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_});
   }
 
  protected:
-  void when_then_expr_impl_general(const common::Expression& when_val,
-                                   const common::Expression& the_result_expr) {
-    if (when_val.operators_size() != 1) {
-      throw std::runtime_error("when expression can only one");
-    }
-    // can only be var or dynamic param
-    auto& when_opr = when_val.operators(0);
-    if (when_opr.item_case() != common::ExprOpr::kConst &&
-        when_opr.item_case() != common::ExprOpr::kParam) {
-      throw std::runtime_error("when expression can only be const or param");
+  bool try_to_find_none(
+      const google::protobuf::RepeatedPtrField<common::ExprOpr>& oprs) {
+    for (auto& opr : oprs) {
+      if (opr.item_case() == common::ExprOpr::kConst) {
+        auto& const_pb = opr.const_();
+        if (const_pb.item_case() == common::Value::kNone) {
+          return true;
+        }
+      }
     }
+    return false;
+  }
 
-    std::string when_key;
+  void when_then_expr_impl_general(const common::Expression& when_val,
+                                   const common::Expression& the_result_expr) {
+    // build when expr from sub_expr
+    std::string when_key_code;
     {
-      if (when_opr.item_case() == common::ExprOpr::kConst) {
-        auto& const_val = when_opr.const_();
-        when_key = value_pb_to_str(const_val);
-      } else {
-        auto& param = when_opr.param();
-        auto param_node_type = when_opr.node_type();
-        auto param_const =
-            param_const_pb_to_param_const(param, param_node_type);
-        VLOG(10) << "receive param const: " << param.DebugString();
-        when_key = param_const.var_name + "_";  // TODO: fix hack
-        construct_params_.push_back(param_const);
-      }
+      auto& when_oprs = when_val.operators();
+      when_key_code = build_sub_expr(when_oprs, false);
     }
 
     // then result expression
     std::string then_result_expr_code;
     {
       auto& else_oprs = the_result_expr.operators();
-      auto expr_code = build_sub_expr(else_oprs);
-      std::stringstream ss;
-      ss << "return (";
-      ss << expr_code;
-      ss << ");" << std::endl;
-      then_result_expr_code = ss.str();
+      if (try_to_find_none(else_oprs) && else_oprs.size() == 1) {
+        then_result_expr_code = "NullRecordCreator<result_t>::GetNull()";
+      } else {
+        then_result_expr_code = build_sub_expr(else_oprs, false);
+      }
     }
 
     // concatenate case when into a if else.
     {
-      std::stringstream ss;
-      ss << "if (" << when_key << ") {" << std::endl;
-      ss << then_result_expr_code;
-      ss << "}" << std::endl;
-      auto tmp_res = ss.str();
+      boost::format formater(CASE_WHEN_EXPR_TEMPLATE_STR);
+      formater % when_key_code % then_result_expr_code;
+      auto tmp_res = formater.str();
       VLOG(10) << "WhenThen expr: " << tmp_res;
       when_then_codes_.emplace_back(std::move(tmp_res));
     }
@@ -176,9 +176,11 @@ class CaseWhenBuilder : public ExprBuilder {
   }
 
   // For each when then expr, we need to build a sub expr.
+  // if set_ret_type is true, add return data types to res_data_types
   std::string build_sub_expr(
-      const google::protobuf::RepeatedPtrField<common::ExprOpr>& oprs) {
-    ExprBuilder expr_builder(ctx_, cur_var_id_, true);
+      const google::protobuf::RepeatedPtrField<common::ExprOpr>& oprs,
+      bool set_ret_type) {
+    ExprBuilder expr_builder(ctx_, true);
     expr_builder.AddAllExprOpr(oprs);
     auto& expr_nodes = expr_builder.GetExprNodes();
     auto& tag_props = expr_builder.GetTagSelectors();
@@ -191,12 +193,38 @@ class CaseWhenBuilder : public ExprBuilder {
     for (auto param_const : param_consts) {
       construct_params_.push_back(param_const);
     }
+    if (set_ret_type) {
+      for (auto i = 0; i < oprs.size(); ++i) {
+        auto cur_opr = oprs[i];
+        auto node_type = cur_opr.node_type();
+        if (node_type.type_case() == common::IrDataType::kDataType) {
+          auto data_type = node_type.data_type();
+          res_data_type_.emplace_back(data_type);
+        } else if (node_type.type_case() == common::IrDataType::TYPE_NOT_SET) {
+          // if node_type is not set, try to find all expr_opr's node type and
+          // emplace_back to res_data_type_
+          if (cur_opr.item_case() == common::ExprOpr::kVars) {
+            auto& vars = cur_opr.vars();
+            for (auto j = 0; j < vars.keys_size(); ++j) {
+              auto var = vars.keys(j);
+              auto var_node_type = var.node_type();
+              CHECK(var_node_type.type_case() == common::IrDataType::kDataType);
+              res_data_type_.emplace_back(var_node_type.data_type());
+            }
+          } else if (cur_opr.item_case() == common::ExprOpr::kConst) {
+            auto const_pb = cur_opr.const_();
+            res_data_type_.emplace_back(common_value_2_data_type(const_pb));
+          } else {
+            LOG(FATAL) << "Only support vars now" << cur_opr.DebugString();
+          }
+        } else {
+          LOG(FATAL) << "Can only accept data type" << node_type.DebugString();
+        }
+      }
+    }
     for (auto func_call_var : func_call_vars) {
       func_call_vars_.push_back(func_call_var);
     }
-    VLOG(10) << "Inc var id from " << cur_var_id_ << " to "
-             << expr_builder.GetCurVarId();
-    cur_var_id_ = expr_builder.GetCurVarId();
 
     std::stringstream ss;
     for (auto& expr_node : expr_nodes) {
@@ -209,7 +237,6 @@ class CaseWhenBuilder : public ExprBuilder {
 
   std::vector<std::string> when_then_codes_;
   std::string else_code_;
-  common::DataType ret_type_;
 };
 
 }  // namespace gs
diff --git a/flex/codegen/src/hqps/hqps_edge_expand_builder.h b/flex/codegen/src/hqps/hqps_edge_expand_builder.h
index e3462bf1cbe2..e11ff3e9ef0a 100644
--- a/flex/codegen/src/hqps/hqps_edge_expand_builder.h
+++ b/flex/codegen/src/hqps/hqps_edge_expand_builder.h
@@ -263,7 +263,7 @@ static std::pair<std::string, std::string> BuildOneLabelEdgeExpandOpt(
     std::string expr_code;
     std::vector<codegen::ParamConst> func_call_param_const;
     std::vector<std::pair<int32_t, std::string>> expr_tag_props;
-    common::DataType unused_expr_ret_type;
+    std::vector<common::DataType> unused_expr_ret_type;
     std::tie(expr_func_name, func_call_param_const, expr_tag_props, expr_code,
              unused_expr_ret_type) = expr_builder.Build();
     VLOG(10) << "Found expr in edge_expand_opt:  " << expr_func_name;
diff --git a/flex/codegen/src/hqps/hqps_expr_builder.h b/flex/codegen/src/hqps/hqps_expr_builder.h
index a2c36ea7c772..a21b51e5182a 100644
--- a/flex/codegen/src/hqps/hqps_expr_builder.h
+++ b/flex/codegen/src/hqps/hqps_expr_builder.h
@@ -49,6 +49,9 @@ static constexpr const char* EXPR_BUILDER_TEMPLATE_STR =
     "    %9%\n"
     "};\n";
 
+static constexpr const char* EXTRACT_TEMPLATE_STR =
+    "gs::DateTimeExtractor<%1%>::extract(%2%)";
+
 // The input variable can have property or not, if property is not present, we
 // take that as a IdKey
 static std::pair<int32_t, std::string> variable_to_tag_id_property_selector(
@@ -107,6 +110,8 @@ static std::string logical_to_str(const common::Logical& logical) {
     return "<=";
   case common::Logical::WITHIN:
     return "< WithIn > ";
+  case common::Logical::ISNULL:
+    return "IsNull";
   default:
     throw std::runtime_error("unknown logical");
   }
@@ -158,7 +163,7 @@ static std::string value_pb_to_str(const common::Value& value) {
   case common::Value::kI64Array:
     return i64_array_pb_to_str(value.i64_array());
   case common::Value::kNone:
-    return NONE_LITERAL;
+    return "gs::NONE";
   default:
     throw std::runtime_error("unknown value type" + value.DebugString());
   }
@@ -166,27 +171,78 @@ static std::string value_pb_to_str(const common::Value& value) {
 
 bool constains_vertex_id(const std::vector<codegen::ParamConst>& params) {
   for (auto& param : params) {
-    if (param.type == codegen::DataType::kVertexId) {
+    if (param.type == codegen::DataType::kVertexId ||
+        param.type == codegen::DataType::kEdgeId) {
       return true;
     }
   }
   return false;
 }
 
+// Evaluate expression and return the result data type.
+static common::DataType eval_expr_return_type(const common::Expression& expr) {
+  std::stack<common::ExprOpr> operator_stack;
+  std::stack<common::ExprOpr> tmp_stack;
+  for (auto& opr : expr.operators()) {
+    auto item_case = opr.item_case();
+    VLOG(10) << "got opr: " << opr.DebugString();
+    if (item_case == common::ExprOpr::kBrace) {
+      auto brace = opr.brace();
+      if (brace == common::ExprOpr::Brace::ExprOpr_Brace_LEFT_BRACE) {
+        operator_stack.push(opr);
+      } else {
+        while (!operator_stack.empty() &&
+               operator_stack.top().item_case() != common::ExprOpr::kBrace) {
+          tmp_stack.push(operator_stack.top());
+          operator_stack.pop();
+        }
+        CHECK(!operator_stack.empty() &&
+              operator_stack.top().item_case() == common::ExprOpr::kBrace &&
+              operator_stack.top().brace() ==
+                  common::ExprOpr::Brace::ExprOpr_Brace_LEFT_BRACE)
+            << "no left brace found";
+        operator_stack.pop();
+      }
+    } else if (item_case == common::ExprOpr::kLogical ||
+               common::ExprOpr::kArith) {
+      operator_stack.push(opr);
+    } else if (item_case == common::ExprOpr::kConst ||
+               item_case == common::ExprOpr::kVar ||
+               item_case == common::ExprOpr::kVars ||
+               item_case == common::ExprOpr::kVarMap ||
+               item_case == common::ExprOpr::kParam) {
+      tmp_stack.push(opr);
+    } else {
+      LOG(WARNING) << "not recognized expr opr: " << opr.DebugString();
+      throw std::runtime_error("not recognized expr opr");
+    }
+  }
+  while (!operator_stack.empty()) {
+    tmp_stack.push(operator_stack.top());
+    operator_stack.pop();
+  }
+  {
+    // print tmp_stack
+    std::stack<common::ExprOpr> tmp_stack_copy = tmp_stack;
+    std::stringstream ss;
+    while (!tmp_stack_copy.empty()) {
+      ss << tmp_stack_copy.top().DebugString() << std::endl;
+      tmp_stack_copy.pop();
+    }
+    VLOG(10) << "tmp_stack: " << ss.str();
+  }
+
+  return tmp_stack.top().node_type().data_type();
+}
+
 // Simlutate the calculation of expression, return the result data type.
 // convert to prefix expression
 
 /*Build a expression struct from expression*/
 class ExprBuilder {
- protected:
-  static constexpr const char* EXPR_OPERATOR_CALL_VAR_NAME = "var";
-
  public:
   ExprBuilder(BuildingContext& ctx, int var_id = 0, bool no_build = false)
-      : ctx_(ctx),
-        cur_var_id_(var_id),
-        res_data_type_(
-            common::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_) {
+      : ctx_(ctx) {
     if (!no_build) {
       // no build indicates whether we will use this builder as a helper.
       // If set to true, we will not let queryClassName and next_expr_name
@@ -196,7 +252,11 @@ class ExprBuilder {
   }
 
   void set_return_type(common::DataType data_type) {
-    res_data_type_ = data_type;
+    res_data_type_.emplace_back(data_type);
+  }
+
+  void add_return_type(common::DataType data_type) {
+    res_data_type_.emplace_back(data_type);
   }
 
   void AddAllExprOpr(
@@ -267,9 +327,9 @@ class ExprBuilder {
       auto param_const = variable_to_param_const(var, ctx_);
       // for each variable, we need add the variable to func_call_vars_.
       // and also set a expr node for it. which is unique.
+      make_var_name_unique(param_const);
       func_call_vars_.push_back(param_const);
-      expr_nodes_.emplace_back(std::string(EXPR_OPERATOR_CALL_VAR_NAME) +
-                               std::to_string(cur_var_id_++));
+      expr_nodes_.emplace_back(param_const.var_name);
 
       // expr_nodes_.emplace_back(param_const.var_name);
       // convert a variable to a tag property,
@@ -295,6 +355,7 @@ class ExprBuilder {
       auto param_const =
           param_const_pb_to_param_const(param_const_pb, param_node_type);
       VLOG(10) << "receive param const: " << param_const_pb.DebugString();
+      make_var_name_unique(param_const);
       construct_params_.push_back(param_const);
       expr_nodes_.emplace_back(param_const.var_name + "_");
       break;
@@ -308,6 +369,59 @@ class ExprBuilder {
       break;
     }
 
+    case common::ExprOpr::kVars: {
+      // When receiving vars, we are to make tuple from these vars.
+      auto vars = opr.vars();
+      std::stringstream ss;
+      ss << "std::tuple{";
+      for (auto i = 0; i < vars.keys_size(); ++i) {
+        auto cur_var = vars.keys(i);
+        auto param_const = variable_to_param_const(cur_var, ctx_);
+        make_var_name_unique(param_const);
+        // for each variable, we need add the variable to func_call_vars_.
+        // and also set a expr node for it. which is unique.
+        func_call_vars_.push_back(param_const);
+        ss << param_const.var_name;
+        if (i < vars.keys_size() - 1) {
+          ss << ",";
+        }
+
+        // expr_nodes_.emplace_back(param_const.var_name);
+        // convert a variable to a tag property,
+        // gs::NamedProperty<gs::Int64>{"prop1"}, saved for later use.
+        tag_selectors_.emplace_back(
+            variable_to_tag_id_property_selector(ctx_, cur_var));
+      }
+      ss << "};";
+      expr_nodes_.emplace_back(ss.str());
+      break;
+    }
+
+    case common::ExprOpr::kExtract: {
+      auto extract = opr.extract();
+      auto interval = extract.interval();
+      auto data_time = extract.data_time();
+      CHECK(data_time.operators_size() == 1)
+          << "Currently only support one var in extract";
+      auto expr_opr = data_time.operators(0);
+      CHECK(expr_opr.item_case() == common::ExprOpr::kVar)
+          << "Currently only support var in extract";
+      auto expr_var = expr_opr.var();
+      auto param_const = variable_to_param_const(expr_var, ctx_);
+      make_var_name_unique(param_const);
+      func_call_vars_.push_back(param_const);
+
+      boost::format formater(EXTRACT_TEMPLATE_STR);
+      formater % interval_to_str(interval) % param_const.var_name;
+      auto extract_node_str = formater.str();
+      expr_nodes_.emplace_back(extract_node_str);
+
+      tag_selectors_.emplace_back(
+          variable_to_tag_id_property_selector(ctx_, expr_var));
+      VLOG(10) << "extract opr: " << extract_node_str;
+      break;
+    }
+
     default:
       LOG(WARNING) << "not recognized expr opr: " << opr.DebugString();
       throw std::runtime_error("not recognized expr opr");
@@ -332,8 +446,6 @@ class ExprBuilder {
     return construct_params_;
   }
 
-  int32_t GetCurVarId() const { return cur_var_id_; }
-
   // 0: function name
   // 1: function call params,
   // 2: tag_property
@@ -341,7 +453,7 @@ class ExprBuilder {
   // 4. return type
   virtual std::tuple<std::string, std::vector<codegen::ParamConst>,
                      std::vector<std::pair<int32_t, std::string>>, std::string,
-                     common::DataType>
+                     std::vector<common::DataType>>
   Build() const {
     // Insert param vars to context.
     for (auto i = 0; i < construct_params_.size(); ++i) {
@@ -414,7 +526,7 @@ class ExprBuilder {
     std::stringstream ss;
     for (int i = 0; i < func_call_vars_.size(); ++i) {
       ss << data_type_2_string(func_call_vars_[i].type) << " "
-         << EXPR_OPERATOR_CALL_VAR_NAME << i;
+         << func_call_vars_[i].var_name;
       if (i != func_call_vars_.size() - 1) {
         ss << ",";
       }
@@ -441,6 +553,28 @@ class ExprBuilder {
     return ss.str();
   }
 
+  void make_var_name_unique(codegen::ParamConst& param_const) {
+    std::unordered_set<std::string> var_names;
+    for (auto& param : construct_params_) {
+      auto res = var_names.insert(param.var_name);
+      CHECK(res.second) << "var name: " << param.var_name
+                        << " already exists, illegal state";
+    }
+    for (auto& param : func_call_vars_) {
+      auto res = var_names.insert(param.var_name);
+      CHECK(res.second) << "var name: " << param.var_name
+                        << " already exists, illegal state";
+    }
+    auto cur_var_name = param_const.var_name;
+    int i = 0;
+    while (var_names.find(cur_var_name) != var_names.end()) {
+      cur_var_name = param_const.var_name + "_" + std::to_string(i);
+      ++i;
+    }
+    param_const.var_name = cur_var_name;
+    VLOG(10) << "make var name unique: " << param_const.var_name;
+  }
+
   // this corresponding to the input params.
   std::vector<codegen::ParamConst> construct_params_;
   // input var list of function call
@@ -451,8 +585,8 @@ class ExprBuilder {
   // component of expression
   std::vector<std::string> expr_nodes_;
   BuildingContext& ctx_;
-  int cur_var_id_;
-  common::DataType res_data_type_;
+  // int cur_var_id_;
+  std::vector<common::DataType> res_data_type_;
 
   std::string class_name_;
 };
diff --git a/flex/codegen/src/hqps/hqps_fold_builder.h b/flex/codegen/src/hqps/hqps_fold_builder.h
index 44e9d4c3d9c4..f270c6a5bbd4 100644
--- a/flex/codegen/src/hqps/hqps_fold_builder.h
+++ b/flex/codegen/src/hqps/hqps_fold_builder.h
@@ -67,7 +67,7 @@ std::pair<std::string, std::string> gen_agg_var_and_code_for_fold(
         VLOG(10) << "aggregate on property " << var_prop.key().name();
         in_prop_names.push_back(var.property().key().name());
         in_prop_types.push_back(
-            common_data_type_pb_2_str(var.node_type().data_type()));
+            single_common_data_type_pb_2_str(var.node_type().data_type()));
       }
     } else {
       // var has no property, which means internal id.
diff --git a/flex/codegen/src/hqps/hqps_get_v_builder.h b/flex/codegen/src/hqps/hqps_get_v_builder.h
index e6a0eb7df13a..f67b8057d38f 100644
--- a/flex/codegen/src/hqps/hqps_get_v_builder.h
+++ b/flex/codegen/src/hqps/hqps_get_v_builder.h
@@ -136,7 +136,7 @@ class GetVOpBuilder {
       auto& expr_oprs = expr.operators();
       expr_builder.AddAllExprOpr(expr_oprs);
       expr_builder.set_return_type(common::DataType::BOOLEAN);
-      common::DataType unused_expr_ret_type;
+      std::vector<common::DataType> unused_expr_ret_type;
       if (!expr_builder.empty()) {
         std::tie(expr_name_, expr_call_param_, tag_propertys_, expr_code_,
                  unused_expr_ret_type) = expr_builder.Build();
diff --git a/flex/codegen/src/hqps/hqps_group_by_builder.h b/flex/codegen/src/hqps/hqps_group_by_builder.h
index b23799ef60cd..0f8f59e4d2f3 100644
--- a/flex/codegen/src/hqps/hqps_group_by_builder.h
+++ b/flex/codegen/src/hqps/hqps_group_by_builder.h
@@ -63,6 +63,8 @@ std::string agg_func_pb_2_str(
     return "gs::AggFunc::TO_LIST";
   case physical::GroupBy::AggFunc::Aggregate::GroupBy_AggFunc_Aggregate_TO_SET:
     return "gs::AggFunc::TO_SET";
+  case physical::GroupBy::AggFunc::Aggregate::GroupBy_AggFunc_Aggregate_FIRST:
+    return "gs::AggFunc::FIRST";
   default:
     LOG(FATAL) << "Unsupported aggregate function";
   }
@@ -94,7 +96,7 @@ std::pair<std::string, std::string> gen_agg_var_and_code(
         VLOG(10) << "aggregate on property " << var_prop.key().name();
         in_prop_names.push_back(var.property().key().name());
         in_prop_types.push_back(
-            common_data_type_pb_2_str(var.node_type().data_type()));
+            single_common_data_type_pb_2_str(var.node_type().data_type()));
       }
     } else {
       // var has no property, which means internal id.
@@ -163,8 +165,8 @@ class GroupByOpBuilder {
       } else if (prop.item_case() == common::Property::kKey) {
         auto& prop_key = prop.key();
         prop_name = prop_key.name();
-        prop_type =
-            common_data_type_pb_2_str(key_alias_key.node_type().data_type());
+        prop_type = single_common_data_type_pb_2_str(
+            key_alias_key.node_type().data_type());
       } else {
         LOG(FATAL)
             << "Current only support key_alias on internal id or property";
diff --git a/flex/codegen/src/hqps/hqps_path_expand_builder.h b/flex/codegen/src/hqps/hqps_path_expand_builder.h
index 1c478886d07d..0031e67b550e 100644
--- a/flex/codegen/src/hqps/hqps_path_expand_builder.h
+++ b/flex/codegen/src/hqps/hqps_path_expand_builder.h
@@ -33,14 +33,22 @@ limitations under the License.
 
 namespace gs {
 
-static constexpr const char* PATH_EXPAND_OP_TEMPLATE_STR =
+static constexpr const char* PATH_EXPAND_V_OP_TEMPLATE_STR =
     "%1%\n"
     "%2%\n"
-    "auto %3% = gs::make_path_expand_opt(std::move(%4%), std::move(%5%), "
+    "auto %3% = gs::make_path_expandv_opt(std::move(%4%), std::move(%5%), "
     "gs::Range(%6%, %7%));\n"
     "auto %8% = Engine::PathExpandV<%9%, %10%>(%11%, std::move(%12%), "
     "std::move(%13%));\n";
 
+static constexpr const char* PATH_EXPAND_PATH_OP_TEMPLATE_STR =
+    "%1%\n"
+    "%2%\n"
+    "auto %3% = gs::make_path_expandv_opt(std::move(%4%), std::move(%5%), "
+    "gs::Range(%6%, %7%));\n"
+    "auto %8% = Engine::PathExpandP<%9%, %10%>(%11%, std::move(%12%), "
+    "std::move(%13%));\n";
+
 std::string path_opt_pb_2_str(
     const physical::PathExpand::PathOpt& path_opt_pb) {
   switch (path_opt_pb) {
@@ -253,6 +261,16 @@ class PathExpandOpBuilder {
     return *this;
   }
 
+  PathExpandOpBuilder& set_output_to_vertices() {
+    output_to_vertices_ = true;
+    return *this;
+  }
+
+  PathExpandOpBuilder& set_output_paths() {
+    output_to_vertices_ = false;
+    return *this;
+  }
+
   std::string Build() const {
     {
       // first put the possible param vars into context
@@ -284,7 +302,13 @@ class PathExpandOpBuilder {
 
     auto append_opt = res_alias_to_append_opt(out_tag_id_);
     auto input_col_str = format_input_col(in_tag_id_);
-    boost::format formater(PATH_EXPAND_OP_TEMPLATE_STR);
+    boost::format formater("");
+    if (output_to_vertices_) {
+      formater = boost::format(PATH_EXPAND_V_OP_TEMPLATE_STR);
+    } else {
+      formater = boost::format(PATH_EXPAND_PATH_OP_TEMPLATE_STR);
+    }
+
     formater % edge_expand_opt_ % getv_opt_code_ % path_expand_opt_var %
         edge_expand_opt_name_ % getv_opt_name_ % range_lower_value %
         range_upper_value % next_ctx_name % append_opt % input_col_str %
@@ -303,6 +327,7 @@ class PathExpandOpBuilder {
   std::string path_opt_str_, result_opt_str_;
   std::vector<LabelT> dst_vertex_labels_;
   internal::Direction direction_;
+  bool output_to_vertices_;  // true: output to vertices, false: output to paths
 };
 
 // edge_expand_opt
@@ -313,7 +338,7 @@ class PathExpandOpBuilder {
 // path_expand_pb itself, while the res_alilas shall be fetch from the later
 // get_v
 template <typename LabelT>
-static std::string BuildPathExpandOp(
+static std::string BuildPathExpandVOp(
     BuildingContext& ctx, const physical::PathExpand& path_expand_pb,
     const google::protobuf::RepeatedPtrField<physical::PhysicalOpr::MetaData>&
         meta_data,
@@ -326,7 +351,7 @@ static std::string BuildPathExpandOp(
   }
 
   // CHECK(!path_expand_pb.has_alias());
-  builder.out_tag(out_tag_id);
+  builder.out_tag(out_tag_id);  // out_tag_id overrides alias
 
   return builder
       .path_expand_opt(path_expand_pb.base().edge_expand(),
@@ -337,7 +362,41 @@ static std::string BuildPathExpandOp(
       .path_opt(path_expand_pb.path_opt())
       .result_opt(path_expand_pb.result_opt())
       .condition(path_expand_pb.condition())
+      .set_output_to_vertices()
+      .Build();
+}
+
+// PathExpand without fusing with getv.
+template <typename LabelT>
+static std::string BuildPathExpandPathOp(
+    BuildingContext& ctx, const physical::PathExpand& path_expand_pb,
+    const google::protobuf::RepeatedPtrField<physical::PhysicalOpr::MetaData>&
+        meta_data) {
+  PathExpandOpBuilder<LabelT> builder(ctx);
+  if (path_expand_pb.has_start_tag()) {
+    builder.in_tag(path_expand_pb.start_tag().value());
+  } else {
+    builder.in_tag(-1);
+  }
+
+  // CHECK(path_expand_pb.has_alias());
+  // if not, alias should be 0?
+  if (path_expand_pb.has_alias()) {
+    builder.out_tag(path_expand_pb.alias().value());
+  } else {
+    builder.out_tag(-1);
+  }
 
+  return builder
+      .path_expand_opt(path_expand_pb.base().edge_expand(),
+                       path_expand_pb.base().get_v(),
+                       meta_data)  // get_v_opt must be called first to
+                                   // provide dst_label ids.
+      .hop_range(path_expand_pb.hop_range())
+      .path_opt(path_expand_pb.path_opt())
+      .result_opt(path_expand_pb.result_opt())
+      .condition(path_expand_pb.condition())
+      .set_output_paths()
       .Build();
 }
 }  // namespace gs
diff --git a/flex/codegen/src/hqps/hqps_project_builder.h b/flex/codegen/src/hqps/hqps_project_builder.h
index b4b84feee443..91f6d6f5a22d 100644
--- a/flex/codegen/src/hqps/hqps_project_builder.h
+++ b/flex/codegen/src/hqps/hqps_project_builder.h
@@ -117,14 +117,15 @@ std::string project_case_when_from_project_mapping(
     throw std::runtime_error("case when sanity check failed");
   }
   CaseWhenBuilder builder(ctx);
-  builder.return_type(data_type)
-      .when_then_exprs(expr_case.when_then_expressions())
+  builder.when_then_exprs(expr_case.when_then_expressions())
       .else_expr(expr_case.else_result_expression());
 
   std::string expr_func_name, expr_code;
   std::vector<codegen::ParamConst> func_construct_param_const;
   std::vector<std::pair<int32_t, std::string>> expr_selectors;
-  common::DataType ret_data_type;  // returned data type for case when building
+  std::vector<common::DataType>
+      ret_data_type;  // returned data type for case when building
+  // ret_data_type is not used.
   std::tie(expr_func_name, func_construct_param_const, expr_selectors,
            expr_code, ret_data_type) = builder.Build();
 
@@ -150,17 +151,17 @@ std::string project_expression_from_project_mapping(
     BuildingContext& ctx, const common::Expression& expr,
     int32_t out_alias_tag) {
   auto expr_builder = ExprBuilder(ctx);
-  CHECK(expr.operators_size() == 3)
-      << "Current only support binary expression for project";
-  CHECK(expr.operators(1).has_node_type());
-  auto data_type_name =
-      common_data_type_pb_2_str(expr.operators(1).node_type().data_type());
-  expr_builder.set_return_type(expr.operators(1).node_type().data_type());
+  VLOG(10) << "Projecting expression: " << expr.DebugString();
+  auto ret_data_type = eval_expr_return_type(expr);
+  LOG(INFO) << "Expression return type: "
+            << single_common_data_type_pb_2_str(ret_data_type);
+
   expr_builder.AddAllExprOpr(expr.operators());
+  expr_builder.set_return_type(ret_data_type);
   std::string expr_func_name, expr_code;
   std::vector<codegen::ParamConst> func_construct_param_const;
   std::vector<std::pair<int32_t, std::string>> expr_selectors;
-  common::DataType unused_expr_ret_type;
+  std::vector<common::DataType> unused_expr_ret_type;
   std::tie(expr_func_name, func_construct_param_const, expr_selectors,
            expr_code, unused_expr_ret_type) = expr_builder.Build();
 
@@ -195,7 +196,6 @@ std::string project_variable_mapping_to_string(BuildingContext& ctx,
     VLOG(10) << "Got case when in projecting";
     auto case_when = expr_op.case_();
     VLOG(10) << case_when.DebugString();
-    CHECK(expr_op.node_type().type_case() == common::IrDataType::kDataType);
     return project_case_when_from_project_mapping(
         ctx, case_when, expr_op.node_type().data_type(), real_res_col_id);
   }
@@ -211,6 +211,9 @@ std::string project_variable_mapping_to_string(BuildingContext& ctx,
         prop_names.push_back(prop.key().name());
         data_types.push_back(
             common_data_type_pb_2_data_type(var.node_type().data_type()));
+      } else if (prop.item_case() == common::Property::kLen) {
+        prop_names.push_back("length");
+        data_types.push_back(codegen::DataType::kLength);
       } else {
         LOG(FATAL) << "Unknown property type" << prop.DebugString();
       }
diff --git a/flex/codegen/src/hqps/hqps_scan_builder.h b/flex/codegen/src/hqps/hqps_scan_builder.h
index 27e27bd92526..22264acfc0f4 100644
--- a/flex/codegen/src/hqps/hqps_scan_builder.h
+++ b/flex/codegen/src/hqps/hqps_scan_builder.h
@@ -136,10 +136,10 @@ class ScanOpBuilder {
           std::string expr_func_name, expr_code;
           std::vector<codegen::ParamConst> func_call_param_const;
           std::vector<std::pair<int32_t, std::string>> expr_tag_props;
-          common::DataType unused_expr_ret_type;
+          std::vector<common::DataType> unused_expr_ret_type;
           std::tie(expr_func_name, func_call_param_const, expr_tag_props,
                    expr_code, unused_expr_ret_type) = expr_builder.Build();
-          VLOG(10) << "Found expr in edge_expand_opt:  " << expr_func_name;
+          VLOG(10) << "Found expr in scan:  " << expr_func_name;
           // generate code.
           ctx_.AddExprCode(expr_code);
           std::string expr_var_name = ctx_.GetNextExprVarName();
diff --git a/flex/codegen/src/hqps/hqps_select_builder.h b/flex/codegen/src/hqps/hqps_select_builder.h
index 1c14d1833250..8764f59cac73 100644
--- a/flex/codegen/src/hqps/hqps_select_builder.h
+++ b/flex/codegen/src/hqps/hqps_select_builder.h
@@ -47,7 +47,7 @@ class SelectOpBuilder {
     std::string func_code;
     std::vector<codegen::ParamConst> func_call_params;
     std::vector<std::pair<int32_t, std::string>> tag_props;
-    common::DataType unused_expr_ret_type;
+    std::vector<common::DataType> unused_expr_ret_type;
     std::tie(expr_name_, func_call_params, tag_props, func_code,
              unused_expr_ret_type) = expr_builder.Build();
 
diff --git a/flex/codegen/src/hqps/hqps_sort_builder.h b/flex/codegen/src/hqps/hqps_sort_builder.h
index cabee24afca0..b81bc108690b 100644
--- a/flex/codegen/src/hqps/hqps_sort_builder.h
+++ b/flex/codegen/src/hqps/hqps_sort_builder.h
@@ -55,7 +55,7 @@ std::string sort_pair_pb_to_order_pair(
   CHECK(pair.key().node_type().type_case() == common::IrDataType::kDataType)
       << "sort ordering pair only support primitive";
   sort_prop_type =
-      common_data_type_pb_2_str(pair.key().node_type().data_type());
+      single_common_data_type_pb_2_str(pair.key().node_type().data_type());
   // the type of sorted property.
   sort_prop_name = pair.key().property().key().name();
 
@@ -77,6 +77,10 @@ class SortOpBuilder {
   SortOpBuilder& range(const algebra::Range& limit) {
     lower_ = limit.lower();
     upper_ = limit.upper();
+    if (upper_ == 0) {
+      LOG(WARNING) << "Receive upper limit 0, set to INT_MAX";
+      upper_ = std::numeric_limits<int32_t>::max();
+    }
 
     VLOG(10) << "Sort Range: " << lower_.value()
              << ", upper: " << upper_.value();
diff --git a/flex/codegen/src/hqps_generator.h b/flex/codegen/src/hqps_generator.h
index e53c36782f1f..257f24602371 100644
--- a/flex/codegen/src/hqps_generator.h
+++ b/flex/codegen/src/hqps_generator.h
@@ -289,6 +289,11 @@ class QueryGenerator {
       // physical::PhysicalOpr::MetaData meta_data; //fake meta
       auto opr = op.opr();
       switch (opr.op_kind_case()) {
+      case physical::PhysicalOpr::Operator::kRoot: {
+        LOG(INFO) << "Skip root_scan";
+        break;
+      }
+
       case physical::PhysicalOpr::Operator::kScan: {  // scan
         // TODO: meta_data is not found in scan
         physical::PhysicalOpr::MetaData meta_data;
@@ -299,6 +304,7 @@ class QueryGenerator {
         ss << BuildScanOp(ctx_, scan_op, meta_data) << std::endl;
         break;
       }
+
       case physical::PhysicalOpr::Operator::kEdge: {  // edge expand
         physical::EdgeExpand real_edge_expand = opr.edge();
         // try to use infomation from later operator
@@ -411,30 +417,30 @@ class QueryGenerator {
       case physical::PhysicalOpr::Operator::kPath: {
         physical::PhysicalOpr::MetaData meta_data;
         LOG(INFO) << "Found a path operator";
-        if (FUSE_PATH_EXPAND_V) {
-          if (i + 1 < size) {
-            auto& path_op = opr.path();
-            auto& next_op = plan_.plan(i + 1).opr();
-            CHECK(next_op.op_kind_case() ==
-                  physical::PhysicalOpr::Operator::kVertex)
-                << "PathExpand must be followed by GetV";
+        auto& path_op = opr.path();
+        if (FUSE_PATH_EXPAND_V && !path_op.has_alias() && (i + 1 < size)) {
+          auto& next_op = plan_.plan(i + 1).opr();
+          if (next_op.op_kind_case() ==
+              physical::PhysicalOpr::Operator::kVertex) {
+            LOG(INFO) << " Fusing path expand and get_v";
             auto& get_v_op = next_op.vertex();
             int32_t get_v_res_alias = -1;
             if (get_v_op.has_alias()) {
               get_v_res_alias = get_v_op.alias().value();
             }
 
-            auto res = BuildPathExpandOp<LabelT>(ctx_, path_op, meta_datas,
-                                                 get_v_res_alias);
+            auto res = BuildPathExpandVOp<LabelT>(ctx_, path_op, meta_datas,
+                                                  get_v_res_alias);
             ss << res;
             i += 1;  // jump one step
             break;
-          } else {
-            LOG(FATAL) << "PathExpand is the last operator";
           }
-        } else {
-          LOG(FATAL) << "Currently not supported: PathExpand without Getv";
         }
+        LOG(INFO) << " PathExpand to Path";
+        // otherwise, just expand path
+        auto res = BuildPathExpandPathOp<LabelT>(ctx_, path_op, meta_datas);
+        ss << res;
+        break;
       }
 
       case physical::PhysicalOpr::Operator::kApply: {
@@ -568,6 +574,7 @@ static std::array<std::string, 4> BuildJoinOp(
         left_task_generator.GenerateSubTask();
     left_res_ctx_name = ctx.GetCurCtxName();
   }
+  LOG(INFO) << "Finish building left code";
 
   {
     // right code
@@ -585,6 +592,7 @@ static std::array<std::string, 4> BuildJoinOp(
       ctx.AddParameterVar(right_param_var);
     }
   }
+  LOG(INFO) << "Finish building right code";
 
   // join code.
   {
@@ -626,6 +634,21 @@ static std::array<std::string, 4> BuildJoinOp(
     cur_ss << ");";
     join_code = cur_ss.str();
   }
+  {
+    // The tags in right ctx should be added to left ctx.
+    // after join, the tags/columns from right ctx will be appended to left
+    // ctx.
+    auto right_tag_inds = right_context.GetTagIdAndIndMapping();
+    auto left_tag_inds = ctx.GetTagIdAndIndMapping();
+    for (auto right_tag : right_tag_inds.GetTagInd2TagIds()) {
+      left_tag_inds.CreateOrGetTagInd(right_tag);
+    }
+    VLOG(10) << "Merging right tag ids to left, got : "
+             << gs::to_string(left_tag_inds.GetTagInd2TagIds()) << std::endl
+             << gs::to_string(left_tag_inds.GetTagId2TagInds());
+    ctx.UpdateTagIdAndIndMapping(left_tag_inds);
+  }
+  LOG(INFO) << "Finish building join code";
   return std::array<std::string, 4>{copy_context_code, left_plan_code,
                                     right_plan_code, join_code};
 }
diff --git a/flex/codegen/src/string_utils.h b/flex/codegen/src/string_utils.h
index b9d4c7e5ce29..4c355e0d7555 100644
--- a/flex/codegen/src/string_utils.h
+++ b/flex/codegen/src/string_utils.h
@@ -42,6 +42,8 @@ static constexpr const char* LABEL_ID_T_CASTER = "(label_id_t)";
 static constexpr const char* EMPTY_TYPE = "grape::EmptyType";
 static constexpr const char* INNER_ID_PROPERTY_NAME = "InnerIdProperty";
 static constexpr const char* VERTEX_ID_T = "vertex_id_t";
+static constexpr const char* EDGE_ID_T = "const DefaultEdge<vertex_id_t>&";
+static constexpr const char* LENGTH_KEY_T = "LengthKey";
 static constexpr const char* MAKE_PROJECT_EXPR = "make_project_expr";
 static constexpr const char* APPEND_OPT_TEMP = "gs::AppendOpt::Temp";
 static constexpr const char* APPEND_OPT_PERSIST = "gs::AppendOpt::Persist";
diff --git a/flex/engines/hqps_db/core/null_record.h b/flex/engines/hqps_db/core/null_record.h
index 3a25e32f2e27..5784736c052f 100644
--- a/flex/engines/hqps_db/core/null_record.h
+++ b/flex/engines/hqps_db/core/null_record.h
@@ -77,11 +77,21 @@ static inline bool IsNull(const T& opt) {
   return opt == NullRecordCreator<T>::GetNull();
 }
 
+template <typename VID_T>
+static inline bool IsNull(const DefaultEdge<VID_T>& edge) {
+  return IsNull(edge.src) || IsNull(edge.dst);
+}
+
 // customized operator ==
 template <typename T>
 bool operator==(const T& lhs, const None& rhs) {
   return IsNull(lhs);
 }
+
+template <typename T>
+bool operator!=(const T& lhs, const None& rhs) {
+  return !IsNull(lhs);
+}
 }  // namespace gs
 
 #endif  // ENGINES_HQPS_ENGINE_NULL_RECORD_H_
\ No newline at end of file
diff --git a/flex/engines/hqps_db/core/operator/get_v.h b/flex/engines/hqps_db/core/operator/get_v.h
index b22849c2fdba..6a421838ade6 100644
--- a/flex/engines/hqps_db/core/operator/get_v.h
+++ b/flex/engines/hqps_db/core/operator/get_v.h
@@ -23,8 +23,8 @@ limitations under the License.
 #include "flex/engines/hqps_db/structures/multi_vertex_set/multi_label_vertex_set.h"
 #include "flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h"
 #include "flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h"
-
 #include "flex/storages/rt_mutable_graph/types.h"
+
 #include "grape/utils/bitset.h"
 
 namespace gs {
@@ -62,6 +62,18 @@ class GetVertex {
     return GetNoPropVSetFromSingleDstEdgeSet(graph, set, std::move(get_v_opt));
   }
 
+  // get no prop v from PathSet.
+  template <typename SET_T, typename LabelT, size_t num_labels,
+            typename EXPRESSION,
+            typename std::enable_if<(SET_T::is_path_set)>::type* = nullptr>
+  static auto GetNoPropVFromPathSet(
+      const GRAPH_INTERFACE& graph, const SET_T& set,
+      GetVOpt<LabelT, num_labels, EXPRESSION>&& get_v_opt) {
+    VLOG(10) << "Get no PropertyV from path set, size: " << set.Size();
+    return GetNoPropVFromPathSetImpl(graph, set, get_v_opt.v_opt_,
+                                     get_v_opt.v_labels_, get_v_opt.filter_);
+  }
+
   // get no prop v from untyped edge set, with no predicate.
   template <typename LabelT, size_t num_labels>
   static auto GetNoPropVFromEdgeSet(
@@ -212,6 +224,44 @@ class GetVertex {
   }
 
  private:
+  // get no prop v from path set.
+  template <size_t num_labels, typename EXPRESSION, typename... SELECTOR>
+  static auto GetNoPropVFromPathSetImpl(
+      const GRAPH_INTERFACE& graph,
+      const CompressedPathSet<vertex_id_t, label_id_t>& set, VOpt v_opt,
+      std::array<label_id_t, num_labels>& req_labels,
+      Filter<EXPRESSION, SELECTOR...>& filter) {
+    auto req_label_vec = array_to_vec(req_labels);
+    auto labels = set.GetLabels();
+    // remove duplicate from labels
+    std::sort(labels.begin(), labels.end());
+    labels.erase(std::unique(labels.begin(), labels.end()), labels.end());
+    // Can only be one label.
+    CHECK(labels.size() == 1);
+    // if req_labels is empty, then use the label from path set.
+    if (req_label_vec.empty()) {
+      req_label_vec.push_back(labels[0]);
+    }
+    // check if the label is in v_labels.
+    auto label = labels[0];
+    auto it = std::find(labels.begin(), labels.end(), label);
+    if (it == labels.end()) {
+      LOG(WARNING) << "Label: " << label << " is not in path labels";
+      // create an empty row vertex set.
+      auto res_set =
+          make_default_row_vertex_set<vertex_id_t, label_id_t>({}, label);
+      // create offsets.
+      auto offsets = std::vector<offset_t>(set.Size() + 1);
+      for (auto i = 0; i < offsets.size(); ++i) {
+        offsets[i] = 0;
+      }
+      return std::make_pair(std::move(res_set), std::move(offsets));
+    }
+    auto property_getters_array =
+        get_prop_getters_from_selectors(graph, labels, filter.selectors_);
+    return set.GetVertices(v_opt, filter.expr_, property_getters_array);
+  }
+
   // User-defined expression
   // for vertex set with multiple labels, i.e. two_label or general vertex set.
   // do project.
diff --git a/flex/engines/hqps_db/core/operator/group_by.h b/flex/engines/hqps_db/core/operator/group_by.h
index eba096469801..40e391538abe 100644
--- a/flex/engines/hqps_db/core/operator/group_by.h
+++ b/flex/engines/hqps_db/core/operator/group_by.h
@@ -480,7 +480,7 @@ class GroupByOp {
       size_t ind = 0;
       if (key_tuple_set.find(key_tuple) != key_tuple_set.end()) {
         // already exist
-        auto ind = key_tuple_set[key_tuple];
+        ind = key_tuple_set[key_tuple];
       } else {
         // not exist
         ind = cur_ind++;
diff --git a/flex/engines/hqps_db/core/operator/path_expand.h b/flex/engines/hqps_db/core/operator/path_expand.h
index 43cf2e2f01cf..683f723392af 100644
--- a/flex/engines/hqps_db/core/operator/path_expand.h
+++ b/flex/engines/hqps_db/core/operator/path_expand.h
@@ -21,6 +21,7 @@ limitations under the License.
 #include "flex/engines/hqps_db/core/params.h"
 #include "flex/engines/hqps_db/core/utils/hqps_utils.h"
 #include "flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h"
+#include "flex/engines/hqps_db/structures/path.h"
 
 #include "flex/storages/rt_mutable_graph/types.h"
 #include "flex/utils/property/column.h"
@@ -47,10 +48,35 @@ class PathExpand {
 
   template <typename... T>
   using vertex_set_t = RowVertexSet<label_id_t, vertex_id_t, T...>;
-  // Path expand to vertices with columns.
 
+  // PathExpandPath
+  template <typename... V_SET_T, typename LabelT, typename EDGE_FILTER_T,
+            typename VERTEX_FILTER_T>
+  static auto PathExpandP(
+      const GRAPH_INTERFACE& graph,
+      const RowVertexSet<LabelT, vertex_id_t, V_SET_T...>& vertex_set,
+      PathExpandPOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T>&&
+          path_expand_opt) {
+    // we can choose different path store type with regard to different
+    // result_opt
+    auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
+    auto& get_v_opt = path_expand_opt.get_v_opt_;
+    auto& range = path_expand_opt.range_;
+
+    auto cur_label = vertex_set.GetLabel();
+
+    std::vector<offset_t> offsets;
+    CompressedPathSet<vertex_id_t, label_id_t> path_set;
+    std::tie(path_set, offsets) = path_expand_from_single_label(
+        graph, cur_label, vertex_set.GetVertices(), range, edge_expand_opt,
+        get_v_opt);
+
+    return std::make_pair(std::move(path_set), std::move(offsets));
+  }
+
+  // Path expand to vertices with columns.
   // PathExpand to vertices with vertex properties also retreived
-  template <typename... V_SET_T, typename EXPR, typename LabelT,
+  template <typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
             typename EDGE_FILTER_T, typename... T,
             typename std::enable_if<(sizeof...(T) > 0)>::type* = nullptr,
             typename RES_SET_T = vertex_set_t<int32_t, T...>,  // int32_t is the
@@ -59,7 +85,8 @@ class PathExpand {
   static RES_T PathExpandV(
       const GRAPH_INTERFACE& graph,
       const RowVertexSet<LabelT, vertex_id_t, V_SET_T...>& vertex_set,
-      PathExpandOpt<LabelT, EXPR, EDGE_FILTER_T, T...>&& path_expand_opt) {
+      PathExpandVOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T, T...>&&
+          path_expand_opt) {
     //
     auto cur_label = vertex_set.GetLabel();
     auto& range = path_expand_opt.range_;
@@ -82,14 +109,15 @@ class PathExpand {
                           std::move(std::get<2>(tuple)));
   }
 
-  // PathExpandV for row vertex set as input.
-  template <typename... V_SET_T, typename EXPR, typename LabelT,
+  // PathExpandV for two_label_vertex set as input.
+  template <typename... V_SET_T, typename VERTEX_FILTER_T, typename LabelT,
             typename EDGE_FILTER_T, typename RES_SET_T = vertex_set_t<int32_t>,
             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, LabelT, V_SET_T...>& vertex_set,
-      PathExpandOpt<LabelT, EXPR, EDGE_FILTER_T>&& path_expand_opt) {
+      PathExpandVOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T>&&
+          path_expand_opt) {
     //
     auto& range = path_expand_opt.range_;
     auto& edge_expand_opt = path_expand_opt.edge_expand_opt_;
@@ -152,14 +180,15 @@ class PathExpand {
     return std::make_pair(std::move(row_vertex_set), std::move(res_offsets));
   }
 
-  // PathExpandV for two_label_vertex set as input.
-  template <typename... V_SET_T, typename EXPR, typename LabelT,
+  // PathExpandV for row vertex set as input.
+  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>>>
   static RES_T PathExpandV(
       const GRAPH_INTERFACE& graph,
       const RowVertexSet<LabelT, vertex_id_t, V_SET_T...>& vertex_set,
-      PathExpandOpt<LabelT, EXPR, EDGE_FILTER_T>&& path_expand_opt) {
+      PathExpandVOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T>&&
+          path_expand_opt) {
     //
     auto cur_label = vertex_set.GetLabel();
     auto& range = path_expand_opt.range_;
@@ -349,6 +378,93 @@ class PathExpand {
   }
 
  private:
+  // Expand Path from single label vertices, only take vertices.
+  template <typename EDGE_FILTER_FUNC, typename VERTEX_FILTER_T,
+            typename... EDATA_T>
+  static auto path_expand_from_single_label(
+      const GRAPH_INTERFACE& graph, label_id_t src_label,
+      const std::vector<vertex_id_t>& vertices_vec, const Range& range,
+      const EdgeExpandOpt<label_id_t, EDGE_FILTER_FUNC>& edge_opt,
+      const SimpleGetVNoPropOpt<label_id_t, VERTEX_FILTER_T>& get_vopt) {
+    std::vector<std::vector<vertex_id_t>> other_vertices;
+    std::vector<std::vector<offset_t>> other_offsets;
+    if (edge_opt.other_label_ != src_label) {
+      LOG(FATAL) << "PathExpand only support one kind labels along path"
+                 << std::to_string(edge_opt.other_label_) << ", "
+                 << std::to_string(src_label);
+    }
+
+    VLOG(10) << "PathExpand with vertices num: " << vertices_vec.size()
+             << " of label: " << std::to_string(src_label)
+             << ", range: " << range.start_ << ", " << range.limit_;
+    CHECK(range.limit_ > range.start_);
+    other_vertices.resize(range.limit_);
+    other_offsets.resize(range.limit_);
+    // distance 0 is the src vertices itself.
+    // init with dist 0
+    {
+      auto& cur_other_vertices = other_vertices[0];
+      auto& cur_other_offsets = other_offsets[0];
+      cur_other_vertices.insert(cur_other_vertices.end(), vertices_vec.begin(),
+                                vertices_vec.end());
+
+      for (auto i = 0; i < vertices_vec.size(); ++i) {
+        cur_other_offsets.emplace_back(i);
+      }
+      cur_other_offsets.emplace_back(vertices_vec.size());
+    }
+    VLOG(10) << " Finish set distance 0 vertices.";
+
+    for (auto i = 1; i < range.limit_; ++i) {
+      auto& cur_other_vertices = other_vertices[i];
+      auto& cur_other_offsets = other_offsets[i];
+      auto& prev_other_vertices = other_vertices[i - 1];
+
+      std::tie(cur_other_vertices, cur_other_offsets) =
+          graph.GetOtherVerticesV2(src_label, edge_opt.other_label_,
+                                   edge_opt.edge_label_, prev_other_vertices,
+                                   gs::to_string(edge_opt.dir_), INT_MAX);
+      VLOG(10) << "PathExpand at distance: " << i << ", got vertices: "
+               << "size : " << cur_other_vertices.size();
+    }
+
+    // create a copy of other_offsets.
+    auto copied_other_offsets(other_offsets);
+    std::vector<label_id_t> labels_vec(range.limit_, src_label);
+    auto path_set = CompressedPathSet<vertex_id_t, label_id_t>(
+        std::move(other_vertices), std::move(other_offsets),
+        std::move(labels_vec), range.start_);
+
+    std::vector<std::vector<offset_t>> offset_amplify(
+        range.limit_, std::vector<offset_t>(copied_other_offsets[0].size(), 0));
+    offset_amplify[0] = copied_other_offsets[0];
+    for (auto i = 1; i < offset_amplify.size(); ++i) {
+      for (auto j = 0; j < offset_amplify[i].size(); ++j) {
+        offset_amplify[i][j] =
+            copied_other_offsets[i][offset_amplify[i - 1][j]];
+      }
+    }
+
+    std::vector<size_t> path_num_cnt;
+    path_num_cnt.resize(vertices_vec.size() + 1, 0);
+    for (auto i = 0; i < vertices_vec.size(); ++i) {
+      for (auto j = range.start_; j < range.limit_; ++j) {
+        auto start = offset_amplify[j][i];
+        auto end = offset_amplify[j][i + 1];
+        path_num_cnt[i] += (end - start);
+      }
+    }
+    std::vector<offset_t> ctx_offsets;
+    ctx_offsets.resize(vertices_vec.size() + 1);
+    ctx_offsets[0] = 0;
+    for (auto i = 0; i < vertices_vec.size(); ++i) {
+      ctx_offsets[i + 1] = ctx_offsets[i] + path_num_cnt[i];
+    }
+    VLOG(10) << "Ctx offsets: " << gs::to_string(ctx_offsets);
+
+    return std::make_pair(std::move(path_set), std::move(ctx_offsets));
+  }
+
   template <typename T, typename... Ts>
   static auto prepend_tuple(std::vector<T>&& first_col,
                             std::vector<std::tuple<Ts...>>&& old_cols) {
diff --git a/flex/engines/hqps_db/core/operator/project.h b/flex/engines/hqps_db/core/operator/project.h
index 90e4f90d3d0d..08ccab28f1c5 100644
--- a/flex/engines/hqps_db/core/operator/project.h
+++ b/flex/engines/hqps_db/core/operator/project.h
@@ -28,6 +28,7 @@ limitations under the License.
 #include "flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h"
 #include "flex/engines/hqps_db/structures/multi_vertex_set/general_vertex_set.h"
 #include "flex/engines/hqps_db/structures/multi_vertex_set/two_label_vertex_set.h"
+#include "flex/engines/hqps_db/structures/path.h"
 
 namespace gs {
 
@@ -463,6 +464,32 @@ class ProjectOp {
     return Collection<T>(std::move(tmp_prop_vec));
   }
 
+  // apply project on path set，the type must be lengthKey
+  template <typename PROP_T, typename VID_T, typename LabelT,
+            typename std::enable_if<std::is_same_v<PROP_T, LengthKey>>::type* =
+                nullptr>
+  static auto apply_single_project_impl(
+      const GRAPH_INTERFACE& graph, CompressedPathSet<VID_T, LabelT>& node,
+      const std::string& prop_name, const std::vector<size_t>& repeat_array) {
+    VLOG(10) << "Finish fetching properties";
+
+    std::vector<typename LengthKey::length_data_type> lengths_vec;
+    auto path_vec = node.get_all_valid_paths();
+    CHECK(path_vec.size() == repeat_array.size());
+    lengths_vec.reserve(path_vec.size());
+    for (auto i = 0; i < path_vec.size(); ++i) {
+      if (repeat_array[i] > 0) {
+        auto length = path_vec[i].length();
+        for (auto j = 0; j < repeat_array[i]; ++j) {
+          lengths_vec.push_back(length);
+        }
+      }
+    }
+
+    return Collection<typename LengthKey::length_data_type>(
+        std::move(lengths_vec));
+  }
+
   // evaluate expression in project op
   template <typename EXPR, typename... ELE, typename... PROP_GETTER>
   static inline auto evaluate_proj_expr(
diff --git a/flex/engines/hqps_db/core/operator/shorest_path.h b/flex/engines/hqps_db/core/operator/shorest_path.h
index 01148b0c705e..ac3a4312e04a 100644
--- a/flex/engines/hqps_db/core/operator/shorest_path.h
+++ b/flex/engines/hqps_db/core/operator/shorest_path.h
@@ -255,7 +255,8 @@ class ShortestPathOp {
     for (auto path : paths) {
       size_t s = path.size();
       std::vector<int32_t> offset(s, 0);
-      Path<vertex_id_t> new_path(std::move(path), std::move(offset));
+      Path<vertex_id_t, label_id_t> new_path(std::move(path),
+                                             std::move(offset));
       path_set.EmplacePath(std::move(new_path));
     }
     return path_set;
diff --git a/flex/engines/hqps_db/core/operator/sink.h b/flex/engines/hqps_db/core/operator/sink.h
index 397b7cd6605e..f8697d615db2 100644
--- a/flex/engines/hqps_db/core/operator/sink.h
+++ b/flex/engines/hqps_db/core/operator/sink.h
@@ -26,6 +26,7 @@
 #include "flex/engines/hqps_db/structures/multi_edge_set/flat_edge_set.h"
 #include "flex/engines/hqps_db/structures/multi_edge_set/general_edge_set.h"
 #include "flex/engines/hqps_db/structures/multi_edge_set/untyped_edge_set.h"
+#include "flex/engines/hqps_db/structures/path.h"
 
 #include "proto_generated_gie/results.pb.h"
 
@@ -122,12 +123,24 @@ void template_set_value(common::Value* value, T v) {
   value->mutable_str()->assign(v.data(), v.size());
 }
 
+template <typename T, typename std::enable_if<
+                          (std::is_same_v<T, std::string>)>::type* = nullptr>
+void template_set_value(common::Value* value, T v) {
+  value->set_str(v.data(), v.size());
+}
+
 template <typename T,
           typename std::enable_if<(std::is_same_v<T, double>)>::type* = nullptr>
 void template_set_value(common::Value* value, T v) {
   value->set_f64(v);
 }
 
+template <typename T, typename std::enable_if<
+                          (std::is_same_v<T, gs::Date>)>::type* = nullptr>
+void template_set_value(common::Value* value, gs::Date v) {
+  value->set_i64(v.milli_second);
+}
+
 template <size_t Is = 0, typename... T>
 void template_set_tuple_value_impl(results::Collection* collection,
                                    const std::tuple<T...>& t) {
@@ -151,7 +164,14 @@ void template_set_tuple_value(results::Collection* collection,
                               const std::vector<T>& t) {
   for (auto i = 0; i < t.size(); ++i) {
     auto cur_ele = collection->add_collection()->mutable_object();
-    template_set_value(cur_ele, t[i]);
+    // if is tuple
+    if constexpr (gs::is_tuple<T>::value) {
+      LOG(WARNING) << "PLEASE FIXME: tuple in vector is not supported "
+                      "yet.";
+      template_set_value(cur_ele, gs::to_string(t[i]));
+    } else {
+      template_set_value(cur_ele, t[i]);
+    }
   }
 }
 
@@ -567,12 +587,12 @@ class SinkOp {
     }
   }
 
-  template <size_t Ind, size_t act_tag_id, typename VID_T, typename LabelT,
-            typename CSR_ITER>
-  static void sink_col_impl(
-      results::CollectiveResults& results_vec,
-      const UnTypedEdgeSet<VID_T, LabelT, CSR_ITER>& edge_set,
-      const std::vector<size_t>& repeat_offsets, int32_t tag_id) {
+  template <size_t Ind, size_t act_tag_id, typename EDGE_SET_T,
+            typename std::enable_if<EDGE_SET_T::is_edge_set>::type* = nullptr>
+  static void sink_col_impl(results::CollectiveResults& results_vec,
+                            const EDGE_SET_T& edge_set,
+                            const std::vector<size_t>& repeat_offsets,
+                            int32_t tag_id) {
     if (repeat_offsets.empty()) {
       CHECK(edge_set.Size() == results_vec.results_size())
           << "size neq " << edge_set.Size() << " "
@@ -623,20 +643,18 @@ class SinkOp {
     }
   }
 
-  // sink for general edge set.
-  template <size_t Ind, size_t act_tag_id, size_t edge_label_num, typename GI,
-            typename VID_T, typename LabelT, typename... PROP_TUPLE>
-  static void sink_col_impl(
-      results::CollectiveResults& results_vec,
-      const GeneralEdgeSet<edge_label_num, GI, VID_T, LabelT, PROP_TUPLE...>&
-          edge_set,
-      const std::vector<size_t>& repeat_offsets, int32_t tag_id) {
+  // sink for compressed path set
+  template <size_t Ind, size_t act_tag_id, typename VID_T, typename LabelT>
+  static void sink_col_impl(results::CollectiveResults& results_vec,
+                            const CompressedPathSet<VID_T, LabelT>& path_set,
+                            const std::vector<size_t>& repeat_offsets,
+                            int32_t tag_id) {
     if (repeat_offsets.empty()) {
-      CHECK(edge_set.Size() == results_vec.results_size())
-          << "size neq " << edge_set.Size() << " "
+      CHECK(path_set.Size() == results_vec.results_size())
+          << "size neq " << path_set.Size() << " "
           << results_vec.results_size();
-      auto iter = edge_set.begin();
-      auto end_iter = edge_set.end();
+      auto iter = path_set.begin();
+      auto end_iter = path_set.end();
       for (auto i = 0; i < results_vec.results_size(); ++i) {
         // auto& row = results_vec[i];
         auto row = results_vec.mutable_results(i);
@@ -646,19 +664,18 @@ class SinkOp {
         auto record = row->mutable_record();
         auto new_col = record->add_columns();
         new_col->mutable_name_or_id()->set_id(tag_id);
-        auto mutable_edge =
-            new_col->mutable_entry()->mutable_element()->mutable_edge();
+        auto mutable_path =
+            new_col->mutable_entry()->mutable_element()->mutable_graph_path();
         CHECK(iter != end_iter);
-        mutable_edge->set_src_id(iter.GetSrc());
-        mutable_edge->set_dst_id(iter.GetDst());
+        auto cur_path = iter.GetElement();
+        add_path_to_pb(cur_path, *mutable_path);
         ++iter;
-        // todo: set properties.
       }
     } else {
-      CHECK(repeat_offsets.size() == edge_set.Size());
+      CHECK(repeat_offsets.size() == path_set.Size());
       size_t cur_ind = 0;
-      auto iter = edge_set.begin();
-      auto end_iter = edge_set.end();
+      auto iter = path_set.begin();
+      auto end_iter = path_set.end();
       for (auto i = 0; i < repeat_offsets.size(); ++i) {
         CHECK(iter != end_iter);
         for (auto j = 0; j < repeat_offsets[i]; ++j) {
@@ -670,16 +687,25 @@ class SinkOp {
           auto record = row->mutable_record();
           auto new_col = record->add_columns();
           new_col->mutable_name_or_id()->set_id(tag_id);
-          auto mutable_edge =
-              new_col->mutable_entry()->mutable_element()->mutable_edge();
-          mutable_edge->set_src_id(iter.GetSrc());
-          mutable_edge->set_dst_id(iter.GetDst());
+          auto mutable_path =
+              new_col->mutable_entry()->mutable_element()->mutable_graph_path();
+          auto cur_path = iter.GetElement();
+          add_path_to_pb(cur_path, *mutable_path);
           // todo: set properties.
         }
         ++iter;
       }
     }
   }
+
+  template <typename VID_T, typename LabelT>
+  static void add_path_to_pb(const Path<VID_T, LabelT>& path,
+                             results::GraphPath& mutable_path) {
+    auto& vertices = path.GetVertices();
+    for (auto i = 0; i < vertices.size(); ++i) {
+      mutable_path.add_path()->mutable_vertex()->set_id(vertices[i]);
+    }
+  }
 };
 }  // namespace gs
 
diff --git a/flex/engines/hqps_db/core/params.h b/flex/engines/hqps_db/core/params.h
index 5d0ceb3b4118..e0344a4db6f8 100644
--- a/flex/engines/hqps_db/core/params.h
+++ b/flex/engines/hqps_db/core/params.h
@@ -15,12 +15,14 @@
 #ifndef ENGINES_HQPS_ENGINE_PARAMS_H_
 #define ENGINES_HQPS_ENGINE_PARAMS_H_
 
+#include <time.h>
 #include <climits>
 #include <iostream>
 #include <string>
 // #include "grape/grape.h"
 #include "flex/engines/hqps_db/core/utils/hqps_type.h"
 #include "flex/storages/rt_mutable_graph/types.h"
+#include "flex/utils/property/types.h"
 
 #include "grape/types.h"
 namespace gs {
@@ -231,6 +233,11 @@ struct LabelKeyProperty {
   LabelKeyProperty(std::string&& n) : name(std::move(n)){};
 };
 
+// Denote the length of a path
+struct LengthKey {
+  using length_data_type = int32_t;
+};
+
 template <typename T>
 struct is_label_key_prop : std::false_type {};
 
@@ -310,6 +317,80 @@ enum ResultOpt {
   AllV = 1,  // Get all the vertex on path. i.e. [1,2,3],[1,2,4]
 };
 
+enum Interval {
+  YEAR = 0,
+  MONTH = 1,
+  DAY = 2,
+  HOUR = 3,
+  MINUTE = 4,
+  SECOND = 5,
+};
+
+template <Interval interval>
+struct DateTimeExtractor;
+
+// Extract Year, month, day, hour, minute, second from Date
+
+template <>
+struct DateTimeExtractor<Interval::YEAR> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_year + 1900;
+  }
+};
+
+template <>
+struct DateTimeExtractor<Interval::MONTH> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_mon + 1;
+  }
+};
+
+template <>
+struct DateTimeExtractor<Interval::DAY> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_mday;
+  }
+};
+
+template <>
+struct DateTimeExtractor<Interval::HOUR> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_hour;
+  }
+};
+
+template <>
+struct DateTimeExtractor<Interval::MINUTE> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_min;
+  }
+};
+
+template <>
+struct DateTimeExtractor<Interval::SECOND> {
+  static int32_t extract(const Date& date) {
+    auto millon_second = date.milli_second / 1000;
+    struct tm tm;
+    gmtime_r((time_t*) (&millon_second), &tm);
+    return tm.tm_sec;
+  }
+};
+
 struct TruePredicate {
   template <typename T>
   bool operator()(T& t) const {
@@ -541,6 +622,8 @@ auto make_edge_expandv_opt(Direction dir, LabelT edge_label, LabelT other_label,
   return EdgeExpandOpt(dir, edge_label, other_label, std::move(func));
 }
 
+// Template can not have to variadic template parameters.
+// so we make filter_t as a tuple.
 template <typename LabelT, size_t num_labels, typename FILTER_T, typename... T>
 struct GetVOpt;
 
@@ -583,6 +666,9 @@ struct GetVOpt<LabelT, num_labels, Filter<EXPR, SELECTOR...>, T...> {
 template <typename LabelT, typename EXPR, typename... T>
 using SimpleGetVOpt = GetVOpt<LabelT, 1, EXPR, T...>;
 
+template <typename LabelT, typename EXPR>
+using SimpleGetVNoPropOpt = GetVOpt<LabelT, 1, EXPR>;
+
 // make get_v opt with labels and props and expr(filters)
 template <typename... T, typename LabelT, size_t num_labels, typename EXPR,
           typename... SELECTOR>
@@ -620,13 +706,13 @@ auto make_getv_opt(VOpt v_opt, std::array<LabelT, num_labels>&& v_labels,
 //       v_opt, std::array<std::string, 1>{v_label});
 // }
 
-// Path expand with only one dst label.
-// Path expand with until condition.
-template <typename LabelT, typename EXPR, typename EDGE_FILTER_T,
+// Path expand with only one dst label, with until condition, and resulting
+// vertices.
+template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T,
           typename UNTIL_CONDITION, typename... T>
 struct PathExpandOptImpl {
   PathExpandOptImpl(EdgeExpandOpt<LabelT, EDGE_FILTER_T>&& edge_expand_opt,
-                    SimpleGetVOpt<LabelT, EXPR, T...>&& get_v_opt,
+                    SimpleGetVOpt<LabelT, VERTEX_FILTER_T, T...>&& get_v_opt,
                     Range&& range, UNTIL_CONDITION&& until_condition,
                     PathOpt path_opt = PathOpt::Arbitrary,
                     ResultOpt result_opt = ResultOpt::EndV)
@@ -638,30 +724,48 @@ struct PathExpandOptImpl {
         result_opt_(result_opt) {}
 
   EdgeExpandOpt<LabelT, EDGE_FILTER_T> edge_expand_opt_;
-  SimpleGetVOpt<LabelT, EXPR, T...> get_v_opt_;
+  SimpleGetVOpt<LabelT, 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 EXPR, typename EDGE_FILTER_T, typename... T>
-using PathExpandOpt =
-    PathExpandOptImpl<LabelT, EXPR, EDGE_FILTER_T, Filter<TruePredicate>, T...>;
+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, typename EDGE_FILTER_T, typename VERTEX_FILTER_T>
+using PathExpandPOpt = PathExpandOptImpl<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T,
+                                         Filter<TruePredicate>>;
 
 // opt used for simple path opt.
-template <typename LabelT, typename EXPR, typename EDGE_FILTER_T,
+template <typename LabelT, typename VERTEX_FILTER_T, typename EDGE_FILTER_T,
           typename UNTIL_CONDITION, typename... T>
 using ShortestPathOpt =
-    PathExpandOptImpl<LabelT, EXPR, EDGE_FILTER_T, UNTIL_CONDITION, T...>;
+    PathExpandOptImpl<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T, UNTIL_CONDITION,
+                      T...>;
 
-template <typename LabelT, typename EXPR, typename EDGE_FILTER_T, typename... T>
-auto make_path_expand_opt(
+template <typename LabelT, typename EDGE_FILTER_T, typename VERTEX_FILTER_T,
+          typename... T>
+auto make_path_expandv_opt(
     EdgeExpandOpt<LabelT, EDGE_FILTER_T>&& edge_expand_opt,
-    SimpleGetVOpt<LabelT, EXPR, T...>&& get_v_opt, Range&& range,
+    SimpleGetVOpt<LabelT, VERTEX_FILTER_T, T...>&& get_v_opt, Range&& range,
+    PathOpt path_opt = PathOpt::Arbitrary,
+    ResultOpt result_opt = ResultOpt::EndV) {
+  return PathExpandVOpt<LabelT, EDGE_FILTER_T, 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,
+    SimpleGetVNoPropOpt<LabelT, VERTEX_FILTER_T>&& get_v_opt, Range&& range,
     PathOpt path_opt = PathOpt::Arbitrary,
     ResultOpt result_opt = ResultOpt::EndV) {
-  return PathExpandOpt<LabelT, EXPR, EDGE_FILTER_T, T...>(
+  return PathExpandPOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T>(
       std::move(edge_expand_opt), std::move(get_v_opt), std::move(range),
       Filter<TruePredicate>(), path_opt, result_opt);
 }
diff --git a/flex/engines/hqps_db/core/sync_engine.h b/flex/engines/hqps_db/core/sync_engine.h
index 60006eedef3a..8975a9b7174b 100644
--- a/flex/engines/hqps_db/core/sync_engine.h
+++ b/flex/engines/hqps_db/core/sync_engine.h
@@ -365,17 +365,18 @@ class SyncEngine : public BaseEngine {
 
   //////////////////////////////////////Path Expand/////////////////////////
   // Path Expand to vertices with columns
-  template <AppendOpt opt, int alias_to_use, typename EXPR, typename CTX_HEAD_T,
-            int cur_alias, int base_tag, typename... CTX_PREV, typename LabelT,
-            typename EDGE_FILTER_T, typename... T,
-            typename RES_SET_T = vertex_set_t<dist_t, T...>,
+  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,
+            typename... T, typename RES_SET_T = vertex_set_t<dist_t, T...>,
             typename RES_T =
                 typename ResultContextT<opt, RES_SET_T, cur_alias, CTX_HEAD_T,
                                         base_tag, CTX_PREV...>::result_t>
   static RES_T PathExpandV(
       const GRAPH_INTERFACE& graph,
       Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
-      PathExpandOpt<LabelT, EXPR, EDGE_FILTER_T, T...>&& path_expand_opt) {
+      PathExpandVOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_T, T...>&&
+          path_expand_opt) {
     if (path_expand_opt.path_opt_ != PathOpt::Arbitrary) {
       LOG(FATAL) << "Only support Arbitrary path now";
     }
@@ -389,47 +390,32 @@ class SyncEngine : public BaseEngine {
     // 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 abondon here.
+    // old context will be abandon here.
   }
 
-  /////////////////////GetV, output vertices with columns //////////////////////
-  // res_alias: the alias of output
-  // alias_to_use: the alias of col of current ctx we use as input.
-  // cur_alias: the  alias of current head node.
-  // num_properties: the properties num to get from vertex. should eq
-  // sizeof...(COL_T)
-  template <
-      AppendOpt opt, int alias_to_use, typename CTX_HEAD_T, int cur_alias,
-      int base_tag, typename... CTX_PREV, typename LabelT, size_t num_labels,
-      typename EXPRESSION, typename... SELECTOR, typename... T,
-      typename std::enable_if<(num_labels > 1 && sizeof...(T) >= 1)>::type* =
-          nullptr>
-  static auto GetV(const GRAPH_INTERFACE& frag,
-                   Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
-                   GetVOpt<LabelT, num_labels, Filter<EXPRESSION, SELECTOR...>,
-                           T...>&& get_v_opt) {
-    auto& select = gs::Get<alias_to_use>(ctx);
-    auto pair = GetVertex<GRAPH_INTERFACE>::GetPropertyV(frag, select,
-                                                         std::move(get_v_opt));
-    return ctx.template AddNode<opt>(std::move(pair.first),
-                                     std::move(pair.second), alias_to_use);
-  }
+  /// 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,
+            typename EDGE_FILTER_T, typename VERTEX_FILTER_T>
+  static auto PathExpandP(
+      const GRAPH_INTERFACE& graph,
+      Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
+      PathExpandPOpt<LabelT, EDGE_FILTER_T, VERTEX_FILTER_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>::PathExpandP(
+        graph, select_node, std::move(path_expand_opt));
 
-  template <
-      AppendOpt opt, int alias_to_use, typename CTX_HEAD_T, int cur_alias,
-      int base_tag, typename... CTX_PREV, typename LabelT, size_t num_labels,
-      typename EXPRESSION, typename... SELECTOR, typename... T,
-      typename std::enable_if<(num_labels == 1 && sizeof...(T) >= 1)>::type* =
-          nullptr>
-  static auto GetV(const GRAPH_INTERFACE& frag,
-                   Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
-                   GetVOpt<LabelT, num_labels, Filter<EXPRESSION, SELECTOR...>,
-                           T...>&& get_v_opt) {
-    auto& select = gs::Get<alias_to_use>(ctx);
-    auto pair = GetVertex<GRAPH_INTERFACE>::GetPropertyV(frag, select,
-                                                         std::move(get_v_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.
   }
 
   // get no props, just filter
@@ -479,6 +465,29 @@ class SyncEngine : public BaseEngine {
                                      std::move(pair.second), alias_to_use);
   }
 
+  // get vertex from path set
+  template <
+      AppendOpt opt, int alias_to_use, typename CTX_HEAD_T, int cur_alias,
+      int base_tag, typename... CTX_PREV, typename LabelT, size_t num_labels,
+      typename EXPRESSION, typename... SELECTOR, typename... T,
+      typename ctx_t = Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>,
+      typename old_node_t = std::remove_reference_t<
+          decltype(std::declval<ctx_t>().template GetNode<alias_to_use>())>,
+      typename std::enable_if<(old_node_t::is_path_set &&
+                               sizeof...(T) == 0)>::type* = nullptr>
+  static auto GetV(const GRAPH_INTERFACE& graph,
+                   Context<CTX_HEAD_T, cur_alias, base_tag, CTX_PREV...>&& ctx,
+                   GetVOpt<LabelT, num_labels, Filter<EXPRESSION, SELECTOR...>,
+                           T...>&& get_v_opt) {
+    auto& select = gs::Get<alias_to_use>(ctx);
+    auto pair = GetVertex<GRAPH_INTERFACE>::GetNoPropVFromPathSet(
+        graph, select, std::move(get_v_opt));
+    VLOG(10) << "new node's size: " << pair.first.Size();
+    //  << ", offset: " << gs::to_string(pair.second);
+    return ctx.template AddNode<opt>(std::move(pair.first),
+                                     std::move(pair.second), alias_to_use);
+  }
+
   //////////////////////////////////////Project/////////////////////////
   // Project current relations to new columns, append or not.
   // TODO: add type infere back:
@@ -511,7 +520,7 @@ class SyncEngine : public BaseEngine {
       LOG(FATAL) << "Current only support topk";
     }
     if (limit_range.limit_ == 0) {
-      LOG(FATAL) << "Current only support empty range";
+      LOG(FATAL) << "Current only support non-empty range";
     }
 
     VLOG(10) << "[Sort: ] Sort with " << sizeof...(ORDER_PAIRS) << " keys";
diff --git a/flex/engines/hqps_db/core/utils/hqps_utils.h b/flex/engines/hqps_db/core/utils/hqps_utils.h
index 0487533c9c48..0d7e90a36dcc 100644
--- a/flex/engines/hqps_db/core/utils/hqps_utils.h
+++ b/flex/engines/hqps_db/core/utils/hqps_utils.h
@@ -971,6 +971,9 @@ struct Edge<VID_T, grape::EmptyType> {
   }
 };
 
+template <typename VID_T>
+using DefaultEdge = Edge<VID_T, grape::EmptyType>;
+
 struct QPSError {
   std::string message;
   explicit QPSError(std::string msg) : message(std::move(msg)) {}
diff --git a/flex/engines/hqps_db/core/utils/keyed.h b/flex/engines/hqps_db/core/utils/keyed.h
index 2bd8dff06708..4b4602088e20 100644
--- a/flex/engines/hqps_db/core/utils/keyed.h
+++ b/flex/engines/hqps_db/core/utils/keyed.h
@@ -138,7 +138,7 @@ struct KeyedT<Collection<T>, PropertySelector<grape::EmptyType>> {
       const PropertySelector<grape::EmptyType>& selector) {
     return builder_t(set);
   }
-  static unkeyed_builder_t create_unkyedkeyed_builder(
+  static unkeyed_builder_t create_unkeyed_builder(
       const Collection<T>& set,
       const PropertySelector<grape::EmptyType>& selector) {
     return unkeyed_builder_t();
@@ -490,6 +490,15 @@ static inline auto insert_into_builder_v2_impl(
   return builder.Insert(ele);
 }
 
+// insert to single label edge label.
+template <typename ELE, typename DATA, typename LabelT, typename VID_T,
+          typename EDATA_T>
+static inline auto insert_into_builder_v2_impl(
+    SingleLabelEdgeSetBuilder<VID_T, LabelT, EDATA_T>& builder, const ELE& ele,
+    const DATA& data) {
+  return builder.Insert(ele);
+}
+
 }  // namespace gs
 
 #endif  // ENGINES_HQPS_ENGINE_KEYED_UTILS_H_
diff --git a/flex/engines/hqps_db/core/utils/props.h b/flex/engines/hqps_db/core/utils/props.h
index 1d1cd5f9076f..311bc39292d5 100644
--- a/flex/engines/hqps_db/core/utils/props.h
+++ b/flex/engines/hqps_db/core/utils/props.h
@@ -32,6 +32,9 @@ class Context;
 template <typename VID_T, typename LabelT, typename T>
 class FlatEdgeSet;
 
+template <typename VID_T, typename LabelT, typename EDATA_T>
+class SingleLabelEdgeSet;
+
 // forward declare general_edge_set
 template <size_t N, typename GI, typename VID_T, typename LabelT, typename... T>
 class GeneralEdgeSet;
@@ -238,6 +241,7 @@ class GeneralVertexSetPropGetter {
   const std::vector<grape::Bitset>& bitset_;
 };
 
+// For EdgeSetInnerIdGetter, we return a wrapped EdgeObject.
 template <int tag_id, typename VID_T, typename EDATA_T>
 class EdgeSetInnerIdGetter {
  public:
@@ -245,7 +249,10 @@ class EdgeSetInnerIdGetter {
 
   template <typename ALL_ELE_T>
   inline auto get_from_all_element(const ALL_ELE_T& all_ele) const {
-    return gs::get_from_tuple<tag_id>(all_ele);
+    auto tuple = gs::get_from_tuple<tag_id>(all_ele);
+    auto src_vid = std::get<0>(tuple);
+    auto dst_vid = std::get<1>(tuple);
+    return Edge<VID_T, grape::EmptyType>(src_vid, dst_vid);
   }
 };
 
@@ -677,6 +684,18 @@ static auto create_prop_getter_impl(
       typename FlatEdgeSet<VID_T, LabelT, EDATA_T>::index_ele_tuple_t>();
 }
 
+// get for common properties for Single label edge set.
+template <int tag_id, typename prop_t, typename GRAPH_INTERFACE, typename VID_T,
+          typename LabelT, typename EDATA_T>
+static auto create_prop_getter_impl(
+    const SingleLabelEdgeSet<VID_T, LabelT, EDATA_T>& set,
+    const GRAPH_INTERFACE& graph, const std::string& prop_name) {
+  // single label edge set is a special kind of flat edge set.
+  return FlatEdgeSetPropGetter<
+      tag_id,
+      typename SingleLabelEdgeSet<VID_T, LabelT, EDATA_T>::index_ele_tuple_t>();
+}
+
 // get for common properties for GeneralEdgeSet
 template <int tag_id, typename prop_t, size_t N, typename GI, typename VID_T,
           typename LabelT, typename... EDATA_T>
@@ -747,6 +766,16 @@ static auto create_prop_getter_from_prop_desc(
   return EdgeSetInnerIdGetter<tag_id, VID_T, EDATA_T>();
 }
 
+// create inner id getter for single_label_edge_set.
+template <typename GRAPH_INTERFACE, typename VID_T, typename LabelT,
+          typename EDATA_T, int tag_id>
+static auto create_prop_getter_from_prop_desc(
+    const GRAPH_INTERFACE& graph,
+    const SingleLabelEdgeSet<VID_T, LabelT, EDATA_T>& set,
+    const InnerIdProperty<tag_id>& inner_id_prop) {
+  return EdgeSetInnerIdGetter<tag_id, VID_T, EDATA_T>();
+}
+
 // get prop for inner id for idKey
 template <typename GRAPH_INTERFACE, typename CTX_HEAD_T, int cur_alias,
           int base_tag, typename... CTX_PREV, int tag_id>
diff --git a/flex/engines/hqps_db/structures/collection.h b/flex/engines/hqps_db/structures/collection.h
index 35f3deee9020..bc462d345c36 100644
--- a/flex/engines/hqps_db/structures/collection.h
+++ b/flex/engines/hqps_db/structures/collection.h
@@ -548,7 +548,7 @@ class MinBuilder {
  public:
   MinBuilder(const Collection<T>& set, const GI& graph,
              PropNameArray<T> prop_names) {
-    vec_.resize(set.Size(), std::numeric_limits<T>::max());
+    // vec_.resize(set.Size(), std::numeric_limits<T>::max());
   }
   MinBuilder() {}
   MinBuilder(size_t cap) { vec_.resize(cap, (T) 0); }
@@ -557,6 +557,9 @@ class MinBuilder {
   template <typename IND_ELE_TUPLE, typename DATA_TUPLE>
   void insert(size_t ind, const IND_ELE_TUPLE& tuple, const DATA_TUPLE& data) {
     const auto& cur_ind_ele = gs::get_from_tuple<tag_id>(tuple);
+    VLOG(10) << "Insert min with ind: " << ind
+             << ", value: " << std::get<1>(cur_ind_ele)
+             << ", vec size: " << vec_.size();
     // just count times.
     while (vec_.size() <= ind) {
       vec_.emplace_back(std::numeric_limits<T>::max());
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 110e0b320bd9..bbd3ca179048 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
@@ -159,6 +159,7 @@ class FlatEdgeSet {
         label_triplet_ind_(std::move(label_triplet_ind)),
         direction_(direction) {
     CHECK(label_triplet_ind_.size() == vec_.size());
+    CHECK(prop_names_.size() == label_triplet_.size());
     uint8_t max_ind = 0;
     for (auto i = 0; i < label_triplet_ind_.size(); ++i) {
       max_ind = std::max(max_ind, label_triplet_ind_[i]);
@@ -391,7 +392,7 @@ class SingleLabelEdgeSetBuilder {
   static constexpr bool is_general_edge_set_builder = false;
   static constexpr bool is_two_label_set_builder = false;
 
-  SingleLabelEdgeSetBuilder(std::array<LabelT, 3>& label_triplet,
+  SingleLabelEdgeSetBuilder(const std::array<LabelT, 3>& label_triplet,
                             std::vector<std::string> prop_names,
                             Direction direction)
       : label_triplet_(label_triplet),
@@ -404,7 +405,8 @@ class SingleLabelEdgeSetBuilder {
   }
 
   result_t Build() {
-    return result_t(std::move(vec_), label_triplet_, prop_names_, direction_);
+    return result_t(std::move(vec_), std::move(label_triplet_), prop_names_,
+                    direction_);
   }
 
  private:
@@ -647,7 +649,6 @@ class SingleLabelEdgeSet {
   std::vector<std::string> prop_names_;
   Direction direction_;
 };
-
 }  // namespace gs
 
 #endif  // ENGINES_HQPS_ENGINE_DS_MULTI_EDGE_SET_FLAT_EDGE_SET_H_
\ No newline at end of file
diff --git a/flex/engines/hqps_db/structures/multi_edge_set/general_edge_set.h b/flex/engines/hqps_db/structures/multi_edge_set/general_edge_set.h
index 098c5aaec861..eccacc2a117d 100644
--- a/flex/engines/hqps_db/structures/multi_edge_set/general_edge_set.h
+++ b/flex/engines/hqps_db/structures/multi_edge_set/general_edge_set.h
@@ -76,8 +76,16 @@ class GeneralEdgeSetBuilder<2, GI, VID_T, LabelT, std::tuple<T...>,
   }
 
   res_t Build() {
-    return res_t(std::move(vec_), edge_label_, src_labels_, dst_label_,
-                 prop_names_, std::move(label_vec_), direction_);
+    std::vector<std::array<LabelT, 3>> label_triplets;
+    label_triplets.emplace_back(
+        std::array<LabelT, 3>{src_labels_[0], dst_label_, dst_label_});
+    label_triplets.emplace_back(
+        std::array<LabelT, 3>{src_labels_[1], dst_label_, dst_label_});
+    std::vector<std::vector<std::string>> prop_names;
+    prop_names.emplace_back(array_to_vec(prop_names_));
+    prop_names.emplace_back(array_to_vec(prop_names_));
+    return res_t(std::move(vec_), std::move(label_triplets), prop_names,
+                 std::move(label_vec_), direction_);
   }
 
  private:
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 9a918a1bf014..6f3a4ef6cb54 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
@@ -852,6 +852,7 @@ subSetWithRemovedIndicesImpl(std::vector<offset_t>& removed_indices,
 template <typename LabelT, typename VID_T, typename... T>
 class RowVertexSetImpl {
  public:
+  using element_type = VID_T;
   using element_t = VID_T;
   using lid_t = VID_T;
   using data_tuple_t = std::tuple<T...>;
@@ -1172,6 +1173,7 @@ template <typename LabelT, typename VID_T>
 class RowVertexSetImpl<LabelT, VID_T, grape::EmptyType> {
  public:
   using element_t = VID_T;
+  using element_type = VID_T;
   using lid_t = VID_T;
   using data_tuple_t = std::tuple<grape::EmptyType>;
   // from this tuple, we can reconstruct the partial set.
@@ -1429,6 +1431,7 @@ class RowVertexSetImpl<LabelT, VID_T, grape::EmptyType> {
   LabelT v_label_;
 };
 
+// VID_T can be std::optional<VID_T> or VID_T directly.
 template <typename LabelT, typename VID_T, typename... T>
 using RowVertexSet = RowVertexSetImpl<LabelT, VID_T, T...>;
 
diff --git a/flex/engines/hqps_db/structures/path.h b/flex/engines/hqps_db/structures/path.h
index 23fb07ec4172..622be8916a80 100644
--- a/flex/engines/hqps_db/structures/path.h
+++ b/flex/engines/hqps_db/structures/path.h
@@ -27,44 +27,85 @@ namespace gs {
 // i.e. :[1], [1,2], [1,2,3]
 
 // Assumes all path share same label
-template <typename VID_T>
+template <typename VID_T, typename LabelT>
 struct Path {
+  using vid_t = VID_T;
+  using label_id_t = LabelT;
   std::vector<VID_T> vids_;
-  std::vector<int32_t> label_ids_;
+  std::vector<label_id_t> label_ids_;
+
+  Path(const Path<VID_T, LabelT>& other)
+      : vids_(other.vids_), label_ids_(other.label_ids_) {}
 
-  Path(std::vector<VID_T>&& vids, std::vector<int32_t>&& label_ids)
+  Path(vid_t vid, label_id_t label_id) {
+    vids_.emplace_back(vid);
+    label_ids_.emplace_back(label_id);
+  }
+
+  Path(std::vector<VID_T>&& vids, std::vector<label_id_t>&& label_ids)
       : vids_(std::move(vids)), label_ids_(std::move(label_ids)) {}
 
-  size_t length() const { return vids_.size() - 1; }
+  inline int32_t length() const {
+    if (vids_.size() > 0) {
+      return vids_.size() - 1;
+    }
+    return 0;
+  }
+
+  void EmplaceBack(VID_T vid, label_id_t label_id) {
+    vids_.emplace_back(vid);
+    label_ids_.emplace_back(label_id);
+  }
 
-  const std::vector<VID_T>& GetVertices() const { return vids_; }
+  inline const std::vector<VID_T>& GetVertices() const { return vids_; }
+
+  inline VID_T GetEnd() const {
+    CHECK(vids_.size() > 0);
+    return vids_.back();
+  }
+  inline VID_T GetStart() const {
+    CHECK(vids_.size() > 0);
+    return vids_.front();
+  }
 
   std::string to_string() const {
     std::stringstream ss;
-    for (auto i = 0; i < vids_.size() - 1; ++i) {
-      ss << vids_[i] << "->";
+    for (int32_t i = 0; i < vids_.size(); ++i) {
+      ss << vids_[i];
+      if (i < vids_.size() - 1) {
+        ss << "->";
+      }
     }
-    ss << vids_[vids_.size() - 1];
     return ss.str();
   }
+
+  bool operator==(const Path& rhs) const {
+    if (vids_.size() != rhs.vids_.size()) {
+      return false;
+    }
+    for (auto i = 0; i < vids_.size(); ++i) {
+      if (vids_[i] != rhs.vids_[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
 };
 
 template <typename VID_T, typename LabelT>
 class PathSetIter {
  public:
   using self_type_t = PathSetIter<VID_T, LabelT>;
-  using index_ele_tuple_t = std::pair<size_t, const Path<VID_T>&>;
+  using index_ele_tuple_t = std::pair<size_t, Path<VID_T, LabelT>>;
 
-  PathSetIter(const std::vector<Path<VID_T>>& paths, size_t ind)
+  PathSetIter(const std::vector<Path<VID_T, LabelT>>& paths, size_t ind)
       : paths_(paths), ind_(ind) {}
 
-  const Path<VID_T>& GetElement() const { return paths_[ind_]; }
+  inline Path<VID_T, LabelT> GetElement() const { return paths_[ind_]; }
 
-  std::tuple<Path<VID_T>> GetData() const {
-    return std::make_tuple(paths_[ind_]);
-  }
+  inline Path<VID_T, LabelT> GetData() const { return paths_[ind_]; }
 
-  index_ele_tuple_t GetIndexElement() const {
+  inline index_ele_tuple_t GetIndexElement() const {
     return std::make_tuple(ind_, paths_[ind_]);
   }
 
@@ -91,7 +132,7 @@ class PathSetIter {
   inline const self_type_t* operator->() const { return this; }
 
  private:
-  const std::vector<Path<VID_T>>& paths_;
+  const std::vector<Path<VID_T, LabelT>>& paths_;
   size_t ind_;
 };
 
@@ -101,16 +142,17 @@ class PathSet {
   using flat_t = PathSet<VID_T, LabelT>;
   using self_type_t = PathSet<VID_T, LabelT>;
   using iterator = PathSetIter<VID_T, LabelT>;
-  using data_tuple_t = std::tuple<Path<VID_T>>;
-  using index_ele_tuple_t = std::pair<size_t, const Path<VID_T>&>;
-  PathSet(std::vector<LabelT>&& labels) : labels_(std::move(labels)){};
+  using data_tuple_t = Path<VID_T, LabelT>;
+  using index_ele_tuple_t = std::pair<size_t, Path<VID_T, LabelT>>;
 
-  PathSet(std::vector<Path<VID_T>>&& paths, std::vector<LabelT>&& labels)
-      : paths_(std::move(paths)), labels_(std::move(labels)) {}
+  PathSet(std::vector<Path<VID_T, LabelT>>&& paths)
+      : paths_(std::move(paths)) {}
 
-  void EmplacePath(Path<VID_T>&& path) { paths_.emplace_back(std::move(path)); }
+  void EmplacePath(Path<VID_T, LabelT>&& path) {
+    paths_.emplace_back(std::move(path));
+  }
 
-  const Path<VID_T>& get(size_t i) const {
+  const Path<VID_T, LabelT>& get(size_t i) const {
     CHECK(i < paths_.size());
     return paths_[i];
   }
@@ -121,9 +163,209 @@ class PathSet {
 
   iterator end() const { return iterator(paths_, paths_.size()); }
 
+ private:
+  std::vector<Path<VID_T, LabelT>> paths_;
+};
+
+template <typename VID_T, typename LabelT>
+class CompressedPathSetIter {
+ public:
+  using self_type_t = CompressedPathSetIter<VID_T, LabelT>;
+  using index_ele_tuple_t = std::pair<size_t, Path<VID_T, LabelT>>;
+
+  CompressedPathSetIter(std::vector<Path<VID_T, LabelT>>&& paths, size_t ind)
+      : paths_(std::move(paths)), ind_(ind) {}
+
+  Path<VID_T, LabelT> GetElement() const { return paths_[ind_]; }
+
+  Path<VID_T, LabelT> GetData() const { return paths_[ind_]; }
+
+  index_ele_tuple_t GetIndexElement() const {
+    return std::make_pair(ind_, paths_[ind_]);
+  }
+
+  inline const self_type_t& operator++() {
+    ++ind_;
+    return *this;
+  }
+
+  // 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:
+  std::vector<Path<VID_T, LabelT>> paths_;
+  size_t ind_;
+};
+
+template <typename VID_T, typename LabelT>
+class CompressedPathSet {
+ public:
+  using vid_t = VID_T;
+  using flat_t = PathSet<VID_T, LabelT>;
+  using self_type_t = CompressedPathSet<VID_T, LabelT>;
+  using iterator = CompressedPathSetIter<VID_T, LabelT>;
+  using ele_tuple_t = Path<VID_T, LabelT>;
+  using data_tuple_t = Path<VID_T, LabelT>;
+  using index_ele_tuple_t = std::pair<size_t, Path<VID_T, LabelT>>;
+  // Empty
+  static constexpr bool is_path_set = true;
+
+  CompressedPathSet() {}
+
+  // We assume we have a dummy head, so min_len_ is 1.
+  // store the path is a csr format.
+  CompressedPathSet(std::vector<std::vector<vid_t>>&& vids,
+                    std::vector<std::vector<offset_t>>&& offsets,
+                    std::vector<LabelT>&& labels, size_t min_len)
+      : vids_(std::move(vids)),
+        offsets_(std::move(offsets)),
+        labels_(std::move(labels)),
+        min_len_(min_len) {
+    CHECK(min_len_ < vids_.size() && min_len_ > 0);
+    // vids.size() is the max length of path.
+    // offset[i].back() is the number of paths with length i.
+    CHECK(vids_.size() == offsets_.size())
+        << "vids and offsets size not match" << vids_.size() << ", "
+        << offsets_.size();
+    CHECK(vids_.size() == labels_.size());
+    for (auto i = 0; i < vids_.size(); ++i) {
+      CHECK(vids_[i].size() == offsets_[i].back());
+    }
+  }
+
+  size_t Size() const {
+    size_t res = 0;
+    for (int32_t i = min_len_; i < offsets_.size(); ++i) {
+      res += offsets_[i].back();
+    }
+    return res;
+  }
+
+  iterator begin() const {
+    auto paths = get_all_valid_paths();
+    VLOG(10) << "got paths of size: " << paths.size();
+    CHECK(paths.size() == Size());
+    return iterator(std::move(paths), 0);
+  }
+
+  iterator end() const {
+    // use a dummy PathSet.
+    std::vector<Path<VID_T, LabelT>> paths;
+    return iterator(std::move(paths), Size());
+  }
+
+  const std::vector<LabelT>& GetLabels() const { return labels_; }
+
+  template <typename FILTER_T, typename PROP_GETTER_T>
+  std::pair<DefaultRowVertexSet<LabelT, vid_t>, std::vector<offset_t>>
+  GetVertices(VOpt vopt, const FILTER_T& expr,
+              const std::vector<PROP_GETTER_T>& prop_getters) const {
+    // get vertices from path set, current we only have one label is path, so we
+    // don't have label params.
+    std::vector<vid_t> vids;
+    std::vector<offset_t> offsets;
+    offsets.reserve(Size() + 1);
+    offsets.emplace_back(0);
+    CHECK(prop_getters.size() == 1) << "only support one label now";
+
+    auto paths = get_all_valid_paths();
+    if (vopt == VOpt::End) {
+      for (auto& path : paths) {
+        vids.emplace_back(path.GetEnd());
+        offsets.emplace_back(vids.size());
+      }
+    } else if (vopt == VOpt::Start) {
+      for (auto& path : paths) {
+        vids.emplace_back(path.GetStart());
+        offsets.emplace_back(vids.size());
+      }
+    } else {
+      LOG(FATAL) << "Not supported vopt: " << gs::to_string(vopt);
+    }
+
+    auto row_set = make_default_row_vertex_set(std::move(vids), labels_[0]);
+    return std::make_pair(row_set, std::move(offsets));
+  }
+
+  std::vector<Path<VID_T, LabelT>> get_all_valid_paths() const {
+    std::vector<Path<VID_T, LabelT>> paths;
+    std::vector<std::vector<Path<VID_T, LabelT>>> paths_by_len;
+    auto path_len = vids_.size();
+    VLOG(10) << "path len: " << path_len;
+    for (auto i = 0; i < path_len; ++i) {
+      std::vector<Path<VID_T, LabelT>> cur_paths;
+      if (i == 0) {
+        for (auto j = 0; j < vids_[i].size(); ++j) {
+          cur_paths.emplace_back(Path<VID_T, LabelT>(vids_[i][j], labels_[i]));
+        }
+      } else {
+        // expand path from last level.
+        for (auto j = 0; j < paths_by_len[i - 1].size(); ++j) {
+          auto path = paths_by_len[i - 1][j];
+          auto last_vid = path.GetVertices().back();
+          CHECK(offsets_[i].back() == vids_[i].size());
+          for (auto k = offsets_[i][j]; k < offsets_[i][j + 1]; ++k) {
+            auto cur_vid = vids_[i][k];
+            // copy the path
+            auto copied_path(path);
+            copied_path.EmplaceBack(cur_vid, labels_[i]);
+            cur_paths.emplace_back(copied_path);
+          }
+        }
+      }
+      VLOG(10) << "got valid paths size: " << cur_paths.size()
+               << " for path len: " << i;
+      paths_by_len.emplace_back(std::move(cur_paths));
+    }
+
+    std::vector<Path<VID_T, LabelT>> res;
+    // rearrange the paths in right order.
+    std::vector<std::vector<offset_t>> offset_amplify(
+        vids_.size(), std::vector<offset_t>(offsets_[0].size(), 0));
+    offset_amplify[0] = offsets_[0];
+    for (auto i = 1; i < offset_amplify.size(); ++i) {
+      for (auto j = 0; j < offset_amplify[i].size(); ++j) {
+        offset_amplify[i][j] = offsets_[i][offset_amplify[i - 1][j]];
+      }
+    }
+    VLOG(10) << "amplify: " << gs::to_string(offset_amplify);
+
+    CHECK(vids_.size() > 0);
+    for (auto i = 0; i < vids_[0].size(); ++i) {
+      // len - 1 is the key.
+      for (auto j = min_len_; j < paths_by_len.size(); ++j) {
+        auto start_ind = offset_amplify[j][i];
+        auto end_ind = offset_amplify[j][i + 1];
+        for (auto k = start_ind; k < end_ind; ++k) {
+          res.emplace_back(paths_by_len[j][k]);
+        }
+      }
+    }
+    VLOG(10) << "Rearrange paths cost: " << res.size()
+             << ", min_len: " << min_len_ << ", path_len: " << path_len;
+
+    return res;
+  }
+
  private:
   std::vector<LabelT> labels_;
-  std::vector<Path<VID_T>> paths_;
+  std::vector<std::vector<VID_T>> vids_;
+  std::vector<std::vector<offset_t>> offsets_;
+  size_t min_len_;
 };
 
 template <typename VID_T, typename LabelT>
diff --git a/flex/engines/http_server/executor.act.cc b/flex/engines/http_server/executor.act.cc
index 977a566c33fa..19ba81823fd4 100644
--- a/flex/engines/http_server/executor.act.cc
+++ b/flex/engines/http_server/executor.act.cc
@@ -89,7 +89,7 @@ seastar::future<query_result> executor::run_hqps_adhoc_query(
   physical::PhysicalPlan plan;
   bool ret = plan.ParseFromArray(str_data, str_length);
   if (ret) {
-    LOG(INFO) << "Parse physical plan: " << plan.DebugString();
+    VLOG(10) << "Parse physical plan: " << plan.DebugString();
   } else {
     LOG(ERROR) << "Fail to parse physical plan";
     return seastar::make_exception_future<query_result>(
diff --git a/flex/engines/http_server/stored_procedure.cc b/flex/engines/http_server/stored_procedure.cc
index 76f1635dd327..8f8d37747376 100644
--- a/flex/engines/http_server/stored_procedure.cc
+++ b/flex/engines/http_server/stored_procedure.cc
@@ -97,7 +97,7 @@ std::vector<std::string> get_yaml_files(const std::string& plugin_dir) {
 }
 
 std::vector<StoredProcedureMeta> parse_from_multiple_yamls(
-  const std::string& plugin_dir,
+    const std::string& plugin_dir,
     const std::vector<std::string>& stored_procedure_yamls) {
   std::vector<StoredProcedureMeta> stored_procedures;
   for (auto cur_yaml : stored_procedure_yamls) {
@@ -112,11 +112,10 @@ std::vector<StoredProcedureMeta> parse_from_multiple_yamls(
       std::string path = root["library"].as<std::string>();
       if (!std::filesystem::exists(path)) {
         // in case the path is relative to plugin_dir, prepend plugin_dir
-        path = plugin_dir + "/"  +path;
+        path = plugin_dir + "/" + path;
         if (!std::filesystem::exists(path)) {
           LOG(ERROR) << "plugin - " << path << " file not found...";
-        }
-        else {
+        } else {
           stored_procedures.push_back({name, path});
         }
       } else {
@@ -177,7 +176,7 @@ std::string load_and_run(int32_t job_id, const std::string& lib_path) {
   gs::Decoder input_decoder(empty.data(), empty.size());
   auto res = temp_stored_procedure->Query(input_decoder);
   LOG(INFO) << "Finish running";
-  LOG(INFO) << res.DebugString();
+  VLOG(10) << res.DebugString();
   std::string res_str;
   res.SerializeToString(&res_str);
   return res_str;
diff --git a/flex/interactive/conf/interactive.properties b/flex/interactive/conf/interactive.properties
index bd1ee716ef00..9dbf43af1ece 100755
--- a/flex/interactive/conf/interactive.properties
+++ b/flex/interactive/conf/interactive.properties
@@ -3,6 +3,6 @@ hiactor.hosts: localhost:10000
 graph.store: exp
 graph.schema: file:../data/ldbc/graph.json
 graph.stored.procedures.uri: file:/tmp
-graph.planner: {"isOn":true,"opt":"RBO","rules":["FilterMatchRule"]}
+graph.planner: {"isOn":true,"opt":"RBO","rules":["FilterIntoJoinRule", "FilterMatchRule", "NotMatchToAntiJoinRule"]}
 gremlin.server.disabled: true
 neo4j.bolt.server.port: 7687
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic10_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic10_adhoc.cypher
new file mode 100644
index 000000000000..57d23617b3ca
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/ic10_adhoc.cypher
@@ -0,0 +1,21 @@
+MATCH (person:PERSON {id: 30786325579101})-[:KNOWS*2..3]-(friend: PERSON)-[:ISLOCATEDIN]->(city:PLACE)
+WHERE NOT friend=person AND NOT (friend:PERSON)-[:KNOWS]-(person :PERSON {id: 30786325579101})
+WITH person, city, friend, friend.birthday as birthday
+WHERE  (birthday.month=7 AND birthday.day>=21) OR
+        (birthday.month=8 AND birthday.day<22)
+WITH DISTINCT friend, city, person
+
+OPTIONAL MATCH (friend : PERSON)<-[:HASCREATOR]-(post:POST)
+WITH friend, city, person, count(post) as postCount
+
+OPTIONAL MATCH (friend)<-[:HASCREATOR]-(post1:POST)-[:HASTAG]->(tag:TAG)<-[:HASINTEREST]-(person: PERSON {id: 30786325579101})
+WITH friend, city, postCount, count(post1) as commonPostCount
+
+RETURN friend.id AS personId,
+       friend.firstName AS personFirstName,
+       friend.lastName AS personLastName,
+       commonPostCount - (postCount - commonPostCount) AS commonInterestScore,
+       friend.gender AS personGender,
+       city.name AS personCityName
+ORDER BY commonInterestScore DESC, personId ASC
+LIMIT 10;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic11_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic11_adhoc.cypher
index 90676b528c24..6321e814d5a4 100644
--- a/flex/resources/queries/ic/adhoc/ic11_adhoc.cypher
+++ b/flex/resources/queries/ic/adhoc/ic11_adhoc.cypher
@@ -2,4 +2,4 @@ MATCH (p:PERSON {id: 6597069812321})-[:KNOWS*1..3]-(friend:PERSON)-[wa:WORKAT]->
 WHERE p <> friend and wa.workFrom < 2011
 with distinct friend as friend, com AS com, wa.workFrom as organizationWorkFromYear 
 ORDER BY organizationWorkFromYear ASC, friend.id ASC, com.name DESC LIMIT 10 
-return friend.id AS personId, friend.firstName AS personFirstName, friend.lastName AS personLastName, com.name as organizationName, organizationWorkFromYear as organizationWorkFromYear
\ No newline at end of file
+return friend.id AS personId, friend.firstName AS personFirstName, friend.lastName AS personLastName, com.name as organizationName, organizationWorkFromYear as organizationWorkFromYear;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic1_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic1_adhoc.cypher
new file mode 100644
index 000000000000..de7f84748dfc
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/ic1_adhoc.cypher
@@ -0,0 +1,40 @@
+MATCH (p: PERSON{id: 30786325579101}) -[k:KNOWS*1..4]-(f: PERSON) where f.firstName = "Ian" AND f.id <> 30786325579101
+with f AS f, length(k) as len
+with f AS f, min(len) as distance
+ORDER  BY distance ASC, f.lastName ASC, f.id ASC
+LIMIT 20
+
+
+OPTIONAL MATCH (f: PERSON)-[workAt:WORKAT]->(company:ORGANISATION)-[:ISLOCATEDIN]->(country:PLACE)
+WITH 
+    f, distance,
+    CASE
+        WHEN company is null Then null
+        ELSE [company.name, workAt.workFrom, country.name]
+    END as companys
+WITH f, collect(companys) as company_info, distance
+
+OPTIONAL MATCH (f: PERSON)-[studyAt:STUDYAT]->(university)-[:ISLOCATEDIN]->(universityCity:PLACE)
+WITH
+  f, company_info, distance,
+	CASE 
+		WHEN university is null Then null
+		ELSE [university.name, studyAt.classYear, universityCity.name]
+	END as universities
+WITH f, collect(universities) as university_info , company_info, distance
+
+MATCH (f:PERSON)-[:ISLOCATEDIN]->(locationCity:PLACE)
+
+return f.id AS friendId,
+        f.lastName AS friendLastName,
+        distance AS distanceFromPerson,
+        f.birthday AS friendBirthday,
+        f.creationDate AS friendCreationDate,
+        f.gender AS friendGender,
+        f.browserUsed AS friendBrowserUsed,
+        f.locationIP AS friendLocationIp,
+        f.email AS friendEmail,
+        f.language AS friendLanguage,
+        locationCity.name AS friendCityName,
+        university_info AS friendUniversities,
+        company_info AS friendCompanies;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic4_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic4_adhoc.cypher
new file mode 100644
index 000000000000..0dd570eaf7af
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/ic4_adhoc.cypher
@@ -0,0 +1,17 @@
+MATCH (person:PERSON {id: 10995116278874})-[:KNOWS]-(friend:PERSON)<-[:HASCREATOR]-(post:POST)-[:HASTAG]->(tag: TAG)
+WITH DISTINCT tag, post
+WITH tag,
+     CASE
+       WHEN post.creationDate < 1340928000000  AND post.creationDate >= 1338508800000 THEN 1
+       ELSE 0
+     END AS valid,
+     CASE
+       WHEN 1338508800000 > post.creationDate THEN 1
+       ELSE 0
+     END AS inValid
+WITH tag, sum(valid) AS postCount, sum(inValid) AS inValidPostCount
+WHERE postCount>0 AND inValidPostCount=0
+
+RETURN tag.name AS tagName, postCount
+ORDER BY postCount DESC, tagName ASC
+LIMIT 10;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic5_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic5_adhoc.cypher
index 0b4572c98051..5d78688f9847 100644
--- a/flex/resources/queries/ic/adhoc/ic5_adhoc.cypher
+++ b/flex/resources/queries/ic/adhoc/ic5_adhoc.cypher
@@ -1,3 +1,3 @@
-MATCH (p:PERSON {id: 40741})-[k:KNOWS*1..3]-(other:PERSON)<-[hasMem:HASMEMBER]-(f:FORUM), 
-(f:FORUM)-[:CONTAINEROF]->(po:POST)-[:HASCREATOR]->(other:PERSON) WHERE hasMem.joinDate > 1346889600000 
-WITH f as f, count(distinct po) AS postCount ORDER BY postCount DESC, f.id ASC LIMIT 20  RETURN f.title as title, postCount 
\ No newline at end of file
+MATCH (p:PERSON {id: 15393162790207})-[k:KNOWS*1..3]-(other:PERSON)<-[hasMem:HASMEMBER]-(f:FORUM), 
+(f:FORUM)-[:CONTAINEROF]->(po:POST)-[:HASCREATOR]->(other:PERSON) WHERE hasMem.joinDate > 1344643200000 
+WITH f as f, count(distinct po) AS postCount ORDER BY postCount DESC, f.id ASC LIMIT 20  RETURN f.title as title, postCount;
\ No newline at end of file
diff --git a/flex/resources/queries/ic/adhoc/ic7_adhoc.cypher b/flex/resources/queries/ic/adhoc/ic7_adhoc.cypher
new file mode 100644
index 000000000000..44d32ed891fc
--- /dev/null
+++ b/flex/resources/queries/ic/adhoc/ic7_adhoc.cypher
@@ -0,0 +1,23 @@
+MATCH (person:PERSON {id: 26388279067534})<-[:HASCREATOR]-(message: POST | COMMENT)<-[like:LIKES]-(liker:PERSON)
+OPTIONAL MATCH (liker: PERSON)-[k:KNOWS]-(person: PERSON {id: 26388279067534})
+WITH liker, message, like.creationDate AS likeTime, person,
+  CASE
+      WHEN k is null THEN true
+      ELSE false
+     END AS isNew
+ORDER BY likeTime DESC, message.id ASC
+WITH liker, person, head(collect(message)) as message, head(collect(likeTime)) AS likeTime, isNew
+RETURN
+    liker.id AS personId,
+    liker.firstName AS personFirstName,
+    liker.lastName AS personLastName,
+    likeTime AS likeCreationDate,
+    message.id AS commentOrPostId,
+    message.content AS messageContent,
+    message.imageFile AS messageImageFile,
+    (likeTime - message.creationDate)/1000/60 AS minutesLatency,
+  	isNew
+ORDER BY
+    likeCreationDate DESC,
+    personId ASC
+LIMIT 20;
\ No newline at end of file
diff --git a/flex/storages/rt_mutable_graph/loader/csv_fragment_loader.cc b/flex/storages/rt_mutable_graph/loader/csv_fragment_loader.cc
index 65606d9aec2c..5ccf212d23b6 100644
--- a/flex/storages/rt_mutable_graph/loader/csv_fragment_loader.cc
+++ b/flex/storages/rt_mutable_graph/loader/csv_fragment_loader.cc
@@ -186,8 +186,11 @@ static void set_vertex_properties(gs::ColumnBase* col,
                                   std::shared_ptr<arrow::ChunkedArray> array,
                                   const std::vector<vid_t>& vids) {
   auto type = array->type();
+  auto col_type = col->type();
   size_t cur_ind = 0;
-  if (type == arrow::int64()) {
+  if (col_type == PropertyType::kInt64) {
+    CHECK(type == arrow::int64())
+        << "Inconsistent data type, expect int64, but got " << type->ToString();
     for (auto j = 0; j < array->num_chunks(); ++j) {
       auto casted =
           std::static_pointer_cast<arrow::Int64Array>(array->chunk(j));
@@ -197,7 +200,9 @@ static void set_vertex_properties(gs::ColumnBase* col,
             std::move(AnyConverter<int64_t>::to_any(casted->Value(k))));
       }
     }
-  } else if (type == arrow::int32()) {
+  } else if (col_type == PropertyType::kInt32) {
+    CHECK(type == arrow::int32())
+        << "Inconsistent data type, expect int32, but got " << type->ToString();
     for (auto j = 0; j < array->num_chunks(); ++j) {
       auto casted =
           std::static_pointer_cast<arrow::Int32Array>(array->chunk(j));
@@ -207,7 +212,10 @@ static void set_vertex_properties(gs::ColumnBase* col,
             std::move(AnyConverter<int32_t>::to_any(casted->Value(k))));
       }
     }
-  } else if (type == arrow::float64()) {
+  } else if (col_type == PropertyType::kDouble) {
+    CHECK(type == arrow::float64())
+        << "Inconsistent data type, expect double, but got "
+        << type->ToString();
     for (auto j = 0; j < array->num_chunks(); ++j) {
       auto casted =
           std::static_pointer_cast<arrow::DoubleArray>(array->chunk(j));
@@ -216,34 +224,48 @@ static void set_vertex_properties(gs::ColumnBase* col,
                      std::move(AnyConverter<double>::to_any(casted->Value(k))));
       }
     }
-  } else if (type == arrow::large_utf8()) {
-    for (auto j = 0; j < array->num_chunks(); ++j) {
-      auto casted =
-          std::static_pointer_cast<arrow::LargeStringArray>(array->chunk(j));
-      for (auto k = 0; k < casted->length(); ++k) {
-        auto str = casted->GetView(k);
-        std::string_view str_view(str.data(), str.size());
-        col->set_any(
-            vids[cur_ind++],
-            std::move(AnyConverter<std::string_view>::to_any(str_view)));
+  } else if (col_type == PropertyType::kString) {
+    CHECK(type == arrow::large_utf8() || type == arrow::utf8())
+        << "Inconsistent data type, expect string, but got "
+        << type->ToString();
+    if (type == arrow::large_utf8()) {
+      for (auto j = 0; j < array->num_chunks(); ++j) {
+        auto casted =
+            std::static_pointer_cast<arrow::LargeStringArray>(array->chunk(j));
+        for (auto k = 0; k < casted->length(); ++k) {
+          auto str = casted->GetView(k);
+          std::string_view str_view(str.data(), str.size());
+          col->set_any(
+              vids[cur_ind++],
+              std::move(AnyConverter<std::string_view>::to_any(str_view)));
+        }
       }
-      for (auto k = 0; k < std::min((int64_t) 10, casted->length()); ++k) {
-        VLOG(10) << "set vertex property: " << vids[k] << ", "
-                 << casted->GetString(k) << " "
-                 << col->get(vids[k]).to_string();
+    } else {
+      for (auto j = 0; j < array->num_chunks(); ++j) {
+        auto casted =
+            std::static_pointer_cast<arrow::StringArray>(array->chunk(j));
+        for (auto k = 0; k < casted->length(); ++k) {
+          auto str = casted->GetView(k);
+          std::string_view str_view(str.data(), str.size());
+          col->set_any(
+              vids[cur_ind++],
+              std::move(AnyConverter<std::string_view>::to_any(str_view)));
+        }
       }
     }
-  } else if (type == arrow::utf8()) {
-    for (auto j = 0; j < array->num_chunks(); ++j) {
-      auto casted =
-          std::static_pointer_cast<arrow::StringArray>(array->chunk(j));
-      for (auto k = 0; k < casted->length(); ++k) {
-        auto str = casted->GetView(k);
-        std::string_view str_view(str.data(), str.size());
-        col->set_any(
-            vids[cur_ind++],
-            std::move(AnyConverter<std::string_view>::to_any(str_view)));
+  } else if (col_type == PropertyType::kDate) {
+    if (type == arrow::int64()) {
+      for (auto j = 0; j < array->num_chunks(); ++j) {
+        auto casted =
+            std::static_pointer_cast<arrow::Int64Array>(array->chunk(j));
+        for (auto k = 0; k < casted->length(); ++k) {
+          col->set_any(vids[cur_ind++],
+                       std::move(AnyConverter<Date>::to_any(casted->Value(k))));
+        }
       }
+    } else {
+      LOG(FATAL) << "Not implemented: converting " << type->ToString() << " to "
+                 << col_type;
     }
   } else {
     LOG(FATAL) << "Not support type: " << type->ToString();
@@ -1175,6 +1197,7 @@ void CSVFragmentLoader::fillEdgeReaderMeta(
     label_t src_label_id, label_t dst_label_id, label_t label_id) const {
   auto time_stamp_parser =
       arrow::TimestampParser::MakeISO8601();  // 2011-08-17T14:26:59.961+0000
+
   convert_options.timestamp_parsers.emplace_back(time_stamp_parser);
 
   put_delimiter_option(loading_config_, parse_options);
diff --git a/flex/storages/rt_mutable_graph/schema.cc b/flex/storages/rt_mutable_graph/schema.cc
index b42f344dd519..95e5e94fabd7 100644
--- a/flex/storages/rt_mutable_graph/schema.cc
+++ b/flex/storages/rt_mutable_graph/schema.cc
@@ -369,7 +369,7 @@ namespace config_parsing {
 static PropertyType StringToPropertyType(const std::string& str) {
   if (str == "int32" || str == DT_SIGNED_INT32) {
     return PropertyType::kInt32;
-  } else if (str == "Date") {
+  } else if (str == "Date" || str == DT_DATE) {
     return PropertyType::kDate;
   } else if (str == "String" || str == DT_STRING) {
     return PropertyType::kString;
@@ -435,13 +435,17 @@ static bool parse_vertex_properties(YAML::Node node,
       return false;
     }
     auto prop_type_node = node[i]["property_type"];
-    if (!prop_type_node["primitive_type"]) {
-      LOG(ERROR) << "type of vertex-" << label_name << " prop-" << i - 1
-                 << " is not primitive...";
-      return false;
-    }
-    if (!get_scalar(prop_type_node, "primitive_type", prop_type_str)) {
-      LOG(ERROR) << "type of vertex-" << label_name << " prop-" << i - 1
+    if (prop_type_node["primitive_type"]) {
+      if (!get_scalar(prop_type_node, "primitive_type", prop_type_str)) {
+        LOG(ERROR) << "type of vertex-" << label_name << " prop-" << i - 1
+                   << " is not specified...";
+        return false;
+      }
+    } else if (prop_type_node["date"]) {
+      auto format = prop_type_node["date"].as<std::string>();
+      prop_type_str = DT_DATE;
+    } else {
+      LOG(ERROR) << "Unknown type of vertex-" << label_name << " prop-" << i - 1
                  << " is not specified...";
       return false;
     }
@@ -481,8 +485,13 @@ static bool parse_edge_properties(YAML::Node node,
     if (node[i]["property_type"]) {
       if (!get_scalar(node[i]["property_type"], "primitive_type",
                       prop_type_str)) {
-        LOG(ERROR) << "Only support primitive type for edge property";
-        return false;
+        if (!get_scalar(node[i]["property_type"], "date", prop_type_str)) {
+          LOG(ERROR) << "Fail to parse property type of edge-" << label_name
+                     << " prop-" << i << " ...";
+          return false;
+        } else {
+          prop_type_str = DT_DATE;
+        }
       }
     } else {
       LOG(ERROR) << "type of edge-" << label_name << " prop-" << i - 1
diff --git a/flex/storages/rt_mutable_graph/types.h b/flex/storages/rt_mutable_graph/types.h
index f73c7cc2fcd3..40c68b7d74a2 100644
--- a/flex/storages/rt_mutable_graph/types.h
+++ b/flex/storages/rt_mutable_graph/types.h
@@ -35,6 +35,7 @@ static constexpr const char* DT_SIGNED_INT32 = "DT_SIGNED_INT32";
 static constexpr const char* DT_SIGNED_INT64 = "DT_SIGNED_INT64";
 static constexpr const char* DT_DOUBLE = "DT_DOUBLE";
 static constexpr const char* DT_STRING = "DT_STRING";
+static constexpr const char* DT_DATE = "DT_DATE";
 
 }  // namespace gs
 
diff --git a/flex/utils/arrow_utils.cc b/flex/utils/arrow_utils.cc
index 50356db839eb..02524df2a7ec 100644
--- a/flex/utils/arrow_utils.cc
+++ b/flex/utils/arrow_utils.cc
@@ -24,7 +24,7 @@ std::shared_ptr<arrow::DataType> PropertyTypeToArrowType(PropertyType type) {
   case PropertyType::kDouble:
     return arrow::float64();
   case PropertyType::kDate:
-    return arrow::timestamp(arrow::TimeUnit::MILLI);
+    return arrow::int64();  // currently we only support use timestamp as input.
   case PropertyType::kString:
     return arrow::large_utf8();
   case PropertyType::kEmpty:
diff --git a/flex/utils/property/types.h b/flex/utils/property/types.h
index aaac320a767f..629e2a79a0f0 100644
--- a/flex/utils/property/types.h
+++ b/flex/utils/property/types.h
@@ -274,6 +274,12 @@ struct AnyConverter<Date> {
     return ret;
   }
 
+  static Any to_any(int64_t value) {
+    Any ret;
+    ret.set_date(value);
+    return ret;
+  }
+
   static AnyValue to_any_value(const Date& value) {
     AnyValue ret;
     ret.d = value;
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/ldbc/LdbcQueries.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/ldbc/LdbcQueries.java
index 22830158c451..342d7d43cacd 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/ldbc/LdbcQueries.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/ldbc/LdbcQueries.java
@@ -21,6 +21,7 @@
 import java.util.List;
 
 public class LdbcQueries {
+
     public static QueryContext get_ldbc_2_test() {
         String query =
                 "MATCH (p:PERSON{id:"
@@ -111,6 +112,36 @@ public static QueryContext get_ldbc_3_test() {
         return new QueryContext(query, Collections.emptyList());
     }
 
+    public static QueryContext get_ldbc_4_test() {
+        String query =
+                "MATCH (person:PERSON {id:"
+                    + " 10995116278874})-[:KNOWS]-(friend:PERSON)<-[:HASCREATOR]-(post:POST)-[:HASTAG]->(tag:"
+                    + " TAG)\n"
+                    + "WITH DISTINCT tag, post\n"
+                    + "WITH tag,\n"
+                    + "     CASE\n"
+                    + "       WHEN post.creationDate < 1340928000000  AND post.creationDate >="
+                    + " 1338508800000 THEN 1\n"
+                    + "       ELSE 0\n"
+                    + "     END AS valid,\n"
+                    + "     CASE\n"
+                    + "       WHEN 1338508800000 > post.creationDate THEN 1\n"
+                    + "       ELSE 0\n"
+                    + "     END AS inValid\n"
+                    + "WITH tag, sum(valid) AS postCount, sum(inValid) AS inValidPostCount\n"
+                    + "WHERE postCount>0 AND inValidPostCount=0\n"
+                    + "\n"
+                    + "RETURN tag.name AS tagName, postCount\n"
+                    + "ORDER BY postCount DESC, tagName ASC\n"
+                    + "LIMIT 10;";
+        List<String> expected =
+                Arrays.asList(
+                        "Record<{tagName: \"Norodom_Sihanouk\", postCount: 3}>",
+                        "Record<{tagName: \"George_Clooney\", postCount: 1}>",
+                        "Record<{tagName: \"Louis_Philippe_I\", postCount: 1}>");
+        return new QueryContext(query, expected);
+    }
+
     public static QueryContext get_ldbc_6_test() {
         String query =
                 "MATCH (person:PERSON"
@@ -136,6 +167,142 @@ public static QueryContext get_ldbc_6_test() {
         return new QueryContext(query, expected);
     }
 
+    public static QueryContext get_ldbc_7_test() {
+        String query =
+                "MATCH (person:PERSON {id: 26388279067534})<-[:HASCREATOR]-(message: POST |"
+                        + " COMMENT)<-[like:LIKES]-(liker:PERSON)\n"
+                        + "OPTIONAL MATCH (liker: PERSON)-[k:KNOWS]-(person: PERSON {id:"
+                        + " 26388279067534})\n"
+                        + "WITH liker, message, like.creationDate AS likeTime, person,\n"
+                        + "  CASE\n"
+                        + "      WHEN k is null THEN true\n"
+                        + "      ELSE false\n"
+                        + "     END AS isNew\n"
+                        + "ORDER BY likeTime DESC, message.id ASC\n"
+                        + "WITH liker, person, head(collect(message)) as message,"
+                        + " head(collect(likeTime)) AS likeTime, isNew\n"
+                        + "RETURN\n"
+                        + "    liker.id AS personId,\n"
+                        + "    liker.firstName AS personFirstName,\n"
+                        + "    liker.lastName AS personLastName,\n"
+                        + "    likeTime AS likeCreationDate,\n"
+                        + "    message.id AS commentOrPostId,\n"
+                        + "    message.content AS messageContent,\n"
+                        + "    message.imageFile AS messageImageFile,\n"
+                        + "    (likeTime - message.creationDate)/1000/60 AS minutesLatency,\n"
+                        + "  \tisNew\n"
+                        + "ORDER BY\n"
+                        + "    likeCreationDate DESC,\n"
+                        + "    personId ASC\n"
+                        + "LIMIT 20;";
+        List<String> expected =
+                Arrays.asList(
+                        "Record<{personId: 32985348834301, personFirstName: \"Anh\","
+                            + " personLastName: \"Nguyen\", likeCreationDate: 1347061100109,"
+                            + " commentOrPostId: 1030792374999, messageContent: \"About David"
+                            + " Foster, llace's unfinished novel, About Paul McCartney,  as a solo"
+                            + " artist and as aAbout \", messageImageFile: \"\", minutesLatency:"
+                            + " 57622, isNew: FALSE}>",
+                        "Record<{personId: 21990232556992, personFirstName: \"Shweta\","
+                            + " personLastName: \"Kumar\", likeCreationDate: 1346511291019,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 5105, isNew: TRUE}>",
+                        "Record<{personId: 15393162790476, personFirstName: \"K.\", personLastName:"
+                            + " \"Rao\", likeCreationDate: 1346463964285, commentOrPostId:"
+                            + " 1030792399080, messageContent: \"About I Only Have Eyes for You, ou"
+                            + " is episode 19 of season two of Buffy the Vampire Sl\","
+                            + " messageImageFile: \"\", minutesLatency: 4317, isNew: TRUE}>",
+                        "Record<{personId: 10995116278184, personFirstName: \"Arjun\","
+                            + " personLastName: \"Kumar\", likeCreationDate: 1346460875173,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 4265, isNew: FALSE}>",
+                        "Record<{personId: 21990232556605, personFirstName: \"Arjun\","
+                            + " personLastName: \"Sen\", likeCreationDate: 1346424083954,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 3652, isNew: TRUE}>",
+                        "Record<{personId: 13194139534142, personFirstName: \"Rahul\","
+                            + " personLastName: \"Reddy\", likeCreationDate: 1346410901910,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 3432, isNew: TRUE}>",
+                        "Record<{personId: 8796093023493, personFirstName: \"Anupam\","
+                            + " personLastName: \"Reddy\", likeCreationDate: 1346402462450,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 3292, isNew: TRUE}>",
+                        "Record<{personId: 24189255811940, personFirstName: \"Arjun\","
+                            + " personLastName: \"Khan\", likeCreationDate: 1346340478487,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 2259, isNew: TRUE}>",
+                        "Record<{personId: 26388279067534, personFirstName: \"Emperor of Brazil\","
+                            + " personLastName: \"Dom Pedro II\", likeCreationDate: 1346329544355,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 2076, isNew: TRUE}>",
+                        "Record<{personId: 687, personFirstName: \"Deepak\", personLastName:"
+                            + " \"Singh\", likeCreationDate: 1346323668418, commentOrPostId:"
+                            + " 1030792399080, messageContent: \"About I Only Have Eyes for You, ou"
+                            + " is episode 19 of season two of Buffy the Vampire Sl\","
+                            + " messageImageFile: \"\", minutesLatency: 1978, isNew: TRUE}>",
+                        "Record<{personId: 13194139533535, personFirstName: \"Shweta\","
+                            + " personLastName: \"Singh\", likeCreationDate: 1346305628827,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 1678, isNew: TRUE}>",
+                        "Record<{personId: 26388279067635, personFirstName: \"John\","
+                                + " personLastName: \"Sheikh\", likeCreationDate: 1346293915386,"
+                                + " commentOrPostId: 893353421832, messageContent: \"\","
+                                + " messageImageFile: \"photo893353421832.jpg\", minutesLatency:"
+                                + " 168543, isNew: FALSE}>",
+                        "Record<{personId: 15393162790406, personFirstName: \"A.\", personLastName:"
+                            + " \"Sharma\", likeCreationDate: 1346280033526, commentOrPostId:"
+                            + " 1030792399080, messageContent: \"About I Only Have Eyes for You, ou"
+                            + " is episode 19 of season two of Buffy the Vampire Sl\","
+                            + " messageImageFile: \"\", minutesLatency: 1251, isNew: TRUE}>",
+                        "Record<{personId: 8796093023060, personFirstName: \"Karim\","
+                            + " personLastName: \"Akhmadiyeva\", likeCreationDate: 1346265643787,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 1011, isNew: TRUE}>",
+                        "Record<{personId: 4398046511667, personFirstName: \"John\","
+                            + " personLastName: \"Chopra\", likeCreationDate: 1346265192141,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 1004, isNew: TRUE}>",
+                        "Record<{personId: 4398046512376, personFirstName: \"Jack\","
+                            + " personLastName: \"Wilson\", likeCreationDate: 1346249759910,"
+                            + " commentOrPostId: 1030792465816, messageContent: \"About Srivijaya,"
+                            + " dated 16 June 682. The kingdom ceased to exiAbout Kingdom of"
+                            + " Hanover,\", messageImageFile: \"\", minutesLatency: 1033, isNew:"
+                            + " TRUE}>",
+                        "Record<{personId: 2199023256816, personFirstName: \"K.\", personLastName:"
+                            + " \"Bose\", likeCreationDate: 1346247410406, commentOrPostId:"
+                            + " 1030792399080, messageContent: \"About I Only Have Eyes for You, ou"
+                            + " is episode 19 of season two of Buffy the Vampire Sl\","
+                            + " messageImageFile: \"\", minutesLatency: 707, isNew: FALSE}>",
+                        "Record<{personId: 26388279067551, personFirstName: \"Anand\","
+                            + " personLastName: \"Rao\", likeCreationDate: 1346235328898,"
+                            + " commentOrPostId: 1030792399080, messageContent: \"About I Only Have"
+                            + " Eyes for You, ou is episode 19 of season two of Buffy the Vampire"
+                            + " Sl\", messageImageFile: \"\", minutesLatency: 506, isNew: TRUE}>",
+                        "Record<{personId: 8796093022764, personFirstName: \"Zheng\","
+                                + " personLastName: \"Xu\", likeCreationDate: 1346205231200,"
+                                + " commentOrPostId: 962072804153, messageContent: \"About Bertolt"
+                                + " Brecht,  – 14 August 1956) was a German poet, playwright, and"
+                                + " theatre director. An influent\", messageImageFile: \"\","
+                                + " minutesLatency: 84681, isNew: TRUE}>",
+                        "Record<{personId: 28587302322631, personFirstName: \"David\","
+                                + " personLastName: \"Fenter\", likeCreationDate: 1346201074886,"
+                                + " commentOrPostId: 893353421832, messageContent: \"\","
+                                + " messageImageFile: \"photo893353421832.jpg\", minutesLatency:"
+                                + " 166995, isNew: FALSE}>");
+        return new QueryContext(query, expected);
+    }
+
     public static QueryContext get_ldbc_8_test() {
         String query =
                 "MATCH (person:PERSON {id:"
@@ -221,6 +388,69 @@ public static QueryContext get_ldbc_8_test() {
         return new QueryContext(query, expected);
     }
 
+    public static QueryContext get_ldbc_10_test() {
+        String query =
+                "MATCH (person:PERSON {id: 30786325579101})-[:KNOWS*2..3]-(friend:"
+                    + " PERSON)-[:ISLOCATEDIN]->(city:PLACE)\n"
+                    + "WHERE NOT friend=person AND NOT (friend:PERSON)-[:KNOWS]-(person :PERSON"
+                    + " {id: 30786325579101})\n"
+                    + "WITH person, city, friend, friend.birthday as birthday\n"
+                    + "WHERE  (birthday.month=7 AND birthday.day>=21) OR\n"
+                    + "        (birthday.month=8 AND birthday.day<22)\n"
+                    + "WITH DISTINCT friend, city, person\n"
+                    + "\n"
+                    + "OPTIONAL MATCH (friend : PERSON)<-[:HASCREATOR]-(post:POST)\n"
+                    + "WITH friend, city, person, count(post) as postCount\n"
+                    + "\n"
+                    + "OPTIONAL MATCH"
+                    + " (friend)<-[:HASCREATOR]-(post1:POST)-[:HASTAG]->(tag:TAG)<-[:HASINTEREST]-(person:"
+                    + " PERSON {id: 30786325579101})\n"
+                    + "WITH friend, city, postCount, count(post1) as commonPostCount\n"
+                    + "\n"
+                    + "RETURN friend.id AS personId,\n"
+                    + "       friend.firstName AS personFirstName,\n"
+                    + "       friend.lastName AS personLastName,\n"
+                    + "       commonPostCount - (postCount - commonPostCount) AS"
+                    + " commonInterestScore,\n"
+                    + "       friend.gender AS personGender,\n"
+                    + "       city.name AS personCityName\n"
+                    + "ORDER BY commonInterestScore DESC, personId ASC\n"
+                    + "LIMIT 10;";
+        List<String> expected =
+                Arrays.asList(
+                        "Record<{personId: 10995116278223, personFirstName: \"Guy\","
+                            + " personLastName: \"Akongo\", commonInterestScore: 0, personGender:"
+                            + " \"female\", personCityName: \"Mokolo\"}>",
+                        "Record<{personId: 19791209301505, personFirstName: \"Henry\","
+                            + " personLastName: \"Smith\", commonInterestScore: 0, personGender:"
+                            + " \"male\", personCityName: \"Coventry\"}>",
+                        "Record<{personId: 32985348833798, personFirstName: \"Otto\","
+                                + " personLastName: \"Kerndlova\", commonInterestScore: 0,"
+                                + " personGender: \"male\", personCityName: \"České_Budějovice\"}>",
+                        "Record<{personId: 6597069767635, personFirstName: \"Thomas Ilenda\","
+                            + " personLastName: \"Lita\", commonInterestScore: -1, personGender:"
+                            + " \"female\", personCityName: \"Bandundu\"}>",
+                        "Record<{personId: 19791209300656, personFirstName: \"Francis\","
+                            + " personLastName: \"Aquino\", commonInterestScore: -1, personGender:"
+                            + " \"male\", personCityName: \"Iligan\"}>",
+                        "Record<{personId: 30786325578904, personFirstName: \"Giuseppe\","
+                            + " personLastName: \"Donati\", commonInterestScore: -2, personGender:"
+                            + " \"male\", personCityName: \"Turin\"}>",
+                        "Record<{personId: 24189255811227, personFirstName: \"Bacary\","
+                            + " personLastName: \"Diop\", commonInterestScore: -4, personGender:"
+                            + " \"male\", personCityName: \"Diourbel\"}>",
+                        "Record<{personId: 17592186045405, personFirstName: \"Wei\","
+                                + " personLastName: \"Li\", commonInterestScore: -5, personGender:"
+                                + " \"female\", personCityName: \"Luoyang\"}>",
+                        "Record<{personId: 13194139533984, personFirstName: \"Annemarie\","
+                                + " personLastName: \"Bos\", commonInterestScore: -8, personGender:"
+                                + " \"female\", personCityName: \"Utrecht\"}>",
+                        "Record<{personId: 26388279068275, personFirstName: \"Jean\","
+                            + " personLastName: \"Berty\", commonInterestScore: -13, personGender:"
+                            + " \"male\", personCityName: \"Buea\"}>");
+        return new QueryContext(query, expected);
+    }
+
     public static QueryContext get_ldbc_12() {
         String query =
                 "MATCH (person:PERSON {id:"


Test patch:
diff --git a/flex/tests/hqps/hqps_cypher_test.sh b/flex/tests/hqps/hqps_cypher_test.sh
index 5696a898f350..da5ce441b545 100644
--- a/flex/tests/hqps/hqps_cypher_test.sh
+++ b/flex/tests/hqps/hqps_cypher_test.sh
@@ -87,7 +87,7 @@ create_ir_conf(){
     echo "graph.schema: ${GS_TEST_DIR}/flex/ldbc-sf01-long-date/ldbc_schema_csr_ic.json" >> ${HQPS_IR_CONF}
     echo "graph.planner.is.on: true" >> ${HQPS_IR_CONF}
     echo "graph.planner.opt: RBO" >> ${HQPS_IR_CONF}
-    echo "graph.planner.rules: FilterMatchRule" >> ${HQPS_IR_CONF}
+    echo "graph.planner.rules: FilterMatchRule,NotMatchToAntiJoinRule" >> ${HQPS_IR_CONF}
     echo "gremlin.server.disabled: true" >> ${HQPS_IR_CONF}
     echo "neo4j.bolt.server.port: 7687" >> ${HQPS_IR_CONF}
 
diff --git a/flex/tests/hqps/match_query.h b/flex/tests/hqps/match_query.h
index 189885b12428..a770d8e4d0e3 100644
--- a/flex/tests/hqps/match_query.h
+++ b/flex/tests/hqps/match_query.h
@@ -519,5 +519,109 @@ class MatchQuery9 : public HqpsAppBase<gs::MutableCSRInterface> {
   }
 };
 
+// test path expand
+
+struct Query10expr0 {
+ public:
+  using result_t = bool;
+  Query10expr0() {}
+
+  inline auto operator()(std::string_view var0, int64_t var1) const {
+    return var0 == "Ian" && var1 != 30786325579101;
+  }
+
+ private:
+};
+
+struct Query10expr1 {
+ public:
+  using result_t = bool;
+  Query10expr1() {}
+
+  inline auto operator()(int64_t var0) const {
+    return (true) && (var0 == 30786325579101);
+  }
+
+ private:
+};
+class MatchQuery10 : 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(
+        Query10expr0(), gs::PropertySelector<std::string_view>("firstName"),
+        gs::PropertySelector<int64_t>("id"));
+    auto ctx0 = Engine::template ScanVertex<gs::AppendOpt::Persist>(
+        graph, 1, std::move(expr0));
+
+    auto edge_expand_opt1 = gs::make_edge_expandv_opt(
+        gs::Direction::Both, (label_id_t) 8, (label_id_t) 1);
+    auto get_v_opt0 = make_getv_opt(gs::VOpt::Itself,
+                                    std::array<label_id_t, 1>{(label_id_t) 1});
+    auto path_opt2 = gs::make_path_expandv_opt(
+        std::move(edge_expand_opt1), std::move(get_v_opt0), gs::Range(1, 4));
+    auto ctx1 = Engine::PathExpandP<gs::AppendOpt::Persist, INPUT_COL_ID(0)>(
+        graph, std::move(ctx0), std::move(path_opt2));
+
+    auto get_v_opt3 = make_getv_opt(gs::VOpt::End, std::array<label_id_t, 0>{});
+    auto ctx2 = Engine::template GetV<gs::AppendOpt::Temp, INPUT_COL_ID(-1)>(
+        graph, std::move(ctx1), std::move(get_v_opt3));
+
+    auto expr2 =
+        gs::make_filter(Query10expr1(), gs::PropertySelector<int64_t>("id"));
+    auto get_v_opt4 = make_getv_opt(
+        gs::VOpt::Itself, std::array<label_id_t, 0>{}, std::move(expr2));
+    auto ctx3 = Engine::template GetV<gs::AppendOpt::Persist, INPUT_COL_ID(-1)>(
+        graph, std::move(ctx2), std::move(get_v_opt4));
+    auto ctx4 = Engine::Project<PROJ_TO_NEW>(
+        graph, std::move(ctx3),
+        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<LengthKey>("length"))});
+
+    GroupKey<0, grape::EmptyType> group_key5(
+        gs::PropertySelector<grape::EmptyType>("None"));
+
+    auto agg_func6 = gs::make_aggregate_prop<gs::AggFunc::MIN>(
+        std::tuple{gs::PropertySelector<grape::EmptyType>("None")},
+        std::integer_sequence<int32_t, 1>{});
+
+    auto ctx5 = Engine::GroupBy(graph, std::move(ctx4), std::tuple{group_key5},
+                                std::tuple{agg_func6});
+    for (auto iter : ctx5) {
+      VLOG(10) << "after groupby: " << gs::to_string(iter.GetAllElement());
+    }
+    auto ctx6 = Engine::Sort(
+        graph, std::move(ctx5), gs::Range(0, 20),
+        std::tuple{
+            gs::OrderingPropPair<gs::SortOrder::ASC, 1, int32_t>(""),
+            gs::OrderingPropPair<gs::SortOrder::ASC, 0, std::string_view>(
+                "lastName"),
+            gs::OrderingPropPair<gs::SortOrder::ASC, 0, int64_t>("id")});
+    auto ctx7 = Engine::Project<PROJ_TO_NEW>(
+        graph, std::move(ctx6),
+        std::tuple{gs::make_mapper_with_variable<INPUT_COL_ID(1)>(
+                       gs::PropertySelector<grape::EmptyType>("")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<std::string_view>("firstName")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<std::string_view>("lastName")),
+                   gs::make_mapper_with_variable<INPUT_COL_ID(0)>(
+                       gs::PropertySelector<int64_t>("id"))});
+    return Engine::Sink(ctx7, std::array<int32_t, 4>{4, 5, 6, 7});
+  }
+  // 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/path_set_test.cc b/flex/tests/hqps/path_set_test.cc
new file mode 100644
index 000000000000..fba936bfc07f
--- /dev/null
+++ b/flex/tests/hqps/path_set_test.cc
@@ -0,0 +1,85 @@
+/** Copyright 2020 Alibaba Group Holding Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <climits>
+#include <vector>
+#include "flex/engines/hqps_db/core/context.h"
+#include "flex/engines/hqps_db/core/params.h"
+#include "flex/engines/hqps_db/structures/multi_vertex_set/row_vertex_set.h"
+#include "flex/storages/rt_mutable_graph/types.h"
+
+#include "flex/engines/hqps_db/database/mutable_csr_interface.h"
+#include "flex/engines/hqps_db/structures/path.h"
+
+#include "grape/types.h"
+
+#include "glog/logging.h"
+
+namespace gs {
+
+void test_path_set() {
+  std::vector<std::vector<vid_t>> vids;
+  std::vector<std::vector<offset_t>> offsets;
+  std::vector<label_t> label_ids;
+
+  vids.emplace_back(std::vector<vid_t>{1, 2});
+  offsets.emplace_back(std::vector<offset_t>{0, 1, 2});
+
+  vids.emplace_back(std::vector<vid_t>{3, 4, 5, 6});
+  offsets.emplace_back(std::vector<offset_t>{0, 2, 4});
+
+  vids.emplace_back(std::vector<vid_t>{7, 8, 9, 10});
+  offsets.emplace_back(std::vector<offset_t>{0, 1, 1, 3, 4});
+
+  label_ids = std::vector<label_t>{1, 1, 1};
+  auto compressed_set = CompressedPathSet<vid_t, label_t>(
+      std::move(vids), std::move(offsets), std::move(label_ids), 1);
+
+  std::vector<Path<vid_t, label_t>> paths;
+  paths.emplace_back(
+      Path<vid_t, label_t>(std::vector<vid_t>{1}, std::vector<label_t>{1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{1, 3},
+                                          std::vector<label_t>{1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{1, 4},
+                                          std::vector<label_t>{1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{1, 3, 7},
+                                          std::vector<label_t>{1, 1, 1}));
+  paths.emplace_back(
+      Path<vid_t, label_t>(std::vector<vid_t>{2}, std::vector<label_t>{1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{2, 5},
+                                          std::vector<label_t>{1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{2, 6},
+                                          std::vector<label_t>{1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{2, 5, 8},
+                                          std::vector<label_t>{1, 1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{2, 5, 9},
+                                          std::vector<label_t>{1, 1, 1}));
+  paths.emplace_back(Path<vid_t, label_t>(std::vector<vid_t>{2, 6, 10},
+                                          std::vector<label_t>{1, 1, 1}));
+
+  CHECK(compressed_set.Size() == paths.size())
+      << "Size not equal." << compressed_set.Size() << ", " << paths.size();
+  size_t cnt = 0;
+  for (auto iter : compressed_set) {
+    auto path = iter.GetElement();
+    CHECK(path == paths[cnt]) << "Path not equal." << path.to_string() << ", "
+                              << paths[cnt].to_string();
+    VLOG(10) << "got path:" << gs::to_string(path);
+    cnt += 1;
+  }
+  LOG(INFO) << "Finish path set test.";
+}
+}  // namespace gs
+
+int main() { gs::test_path_set(); }
diff --git a/flex/tests/hqps/query_test.cc b/flex/tests/hqps/query_test.cc
index 13d1850d3e49..1674238b978a 100644
--- a/flex/tests/hqps/query_test.cc
+++ b/flex/tests/hqps/query_test.cc
@@ -18,6 +18,8 @@
 #include "flex/tests/hqps/match_query.h"
 #include "flex/tests/hqps/sample_query.h"
 
+#include <time.h>
+
 int main(int argc, char** argv) {
   // <oid> <label_name>
   if (argc != 4) {
@@ -186,5 +188,18 @@ int main(int argc, char** argv) {
     LOG(INFO) << "Finish MatchQuery9 test";
   }
 
+  {
+    gs::MatchQuery10 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 MatchQuery10 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/IrLdbcTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/IrLdbcTest.java
index 919f634990b1..c13694b8815b 100644
--- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/IrLdbcTest.java
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/IrLdbcTest.java
@@ -16,6 +16,8 @@
 
 package com.alibaba.graphscope.cypher.integration.ldbc;
 
+import static org.junit.Assume.assumeTrue;
+
 import com.alibaba.graphscope.cypher.integration.suite.ldbc.LdbcQueries;
 import com.alibaba.graphscope.cypher.integration.suite.ldbc.QueryContext;
 
@@ -51,6 +53,14 @@ public void run_ldbc_3_test() {
         Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
     }
 
+    @Test
+    public void run_ldbc_4_test() {
+        assumeTrue("hiactor".equals(System.getenv("ENGINE_TYPE")));
+        QueryContext testQuery = LdbcQueries.get_ldbc_4_test();
+        Result result = session.run(testQuery.getQuery());
+        Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
+    }
+
     @Test
     public void run_ldbc_6_test() {
         QueryContext testQuery = LdbcQueries.get_ldbc_6_test();
@@ -58,6 +68,14 @@ public void run_ldbc_6_test() {
         Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
     }
 
+    @Test
+    public void run_ldbc_7_test() {
+        assumeTrue("hiactor".equals(System.getenv("ENGINE_TYPE")));
+        QueryContext testQuery = LdbcQueries.get_ldbc_7_test();
+        Result result = session.run(testQuery.getQuery());
+        Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
+    }
+
     // @Test
     // public void run_ldbc_8_test() {
     //     QueryContext testQuery = LdbcQueries.get_ldbc_8_test();
@@ -65,6 +83,14 @@ public void run_ldbc_6_test() {
     //     Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
     // }
 
+    @Test
+    public void run_ldbc_10_test() {
+        assumeTrue("hiactor".equals(System.getenv("ENGINE_TYPE")));
+        QueryContext testQuery = LdbcQueries.get_ldbc_10_test();
+        Result result = session.run(testQuery.getQuery());
+        Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
+    }
+
     @Test
     public void run_ldbc_12_test() {
         QueryContext testQuery = LdbcQueries.get_ldbc_12();