diff --git a/src/executor/query/GetNeighborsExecutor.cpp b/src/executor/query/GetNeighborsExecutor.cpp
index 882c17e1f3f44d206220c4ef4070db2e5a3739d0..ae1bd954577999dd24c2bd506c898b32af166ce6 100644
--- a/src/executor/query/GetNeighborsExecutor.cpp
+++ b/src/executor/query/GetNeighborsExecutor.cpp
@@ -97,26 +97,27 @@ folly::Future<Status> GetNeighborsExecutor::getNeighbors() {
                        gn_->filter())
         .via(runner())
         .ensure([this, getNbrTime]() {
+            SCOPED_TIMER(&execTime_);
+            auto time = getNbrTime.elapsedInUSec();
             if (otherStats_ != nullptr) {
-                otherStats_->emplace("total_rpc_time",
-                                     folly::stringPrintf("%lu(us)", getNbrTime.elapsedInUSec()));
+                otherStats_->emplace("total_rpc_time", folly::stringPrintf("%lu(us)", time));
             }
-            VLOG(1) << "Get neighbors time: " << getNbrTime.elapsedInUSec() << "us";
+            VLOG(1) << "Get neighbors time: " << time << "us";
         })
         .then([this](StorageRpcResponse<GetNeighborsResponse>&& resp) {
+            SCOPED_TIMER(&execTime_);
             if (otherStats_ != nullptr) {
                 auto& hostLatency = resp.hostLatency();
                 for (size_t i = 0; i < hostLatency.size(); ++i) {
                     auto& info = hostLatency[i];
-                    otherStats_->emplace(folly::stringPrintf("%s exec/total/vertices",
-                                                             std::get<0>(info).toString().c_str()),
-                                         folly::stringPrintf("%d(us)/%d(us)/%lu,",
-                                                             std::get<1>(info),
-                                                             std::get<2>(info),
-                                                             resp.responses()[i].vertices.size()));
+                    const auto& host = std::get<0>(info).toString();
+                    const auto& key = folly::stringPrintf("%s exec/total/vertices", host.c_str());
+                    auto numVertices = resp.responses()[i].vertices.size();
+                    const auto& value = folly::stringPrintf(
+                        "%d(us)/%d(us)/%lu", std::get<1>(info), std::get<2>(info), numVertices);
+                    otherStats_->emplace(key, value);
                 }
             }
-            SCOPED_TIMER(&execTime_);
             return handleResponse(resp);
         });
 }
diff --git a/src/executor/query/GetVerticesExecutor.cpp b/src/executor/query/GetVerticesExecutor.cpp
index c0ad0d43a27a9c064705800d14a8b28a6ee928e1..82208ea9373333a695254413a6c46ec120eb2baf 100644
--- a/src/executor/query/GetVerticesExecutor.cpp
+++ b/src/executor/query/GetVerticesExecutor.cpp
@@ -72,17 +72,18 @@ folly::Future<Status> GetVerticesExecutor::getVertices() {
                    gv->filter())
         .via(runner())
         .ensure([this, getPropsTime]() {
+            SCOPED_TIMER(&execTime_);
+            auto time = getPropsTime.elapsedInUSec();
             if (otherStats_ != nullptr) {
-                otherStats_->emplace("total_rpc",
-                                     folly::stringPrintf("%lu(us)", getPropsTime.elapsedInUSec()));
+                otherStats_->emplace("total_rpc", folly::stringPrintf("%lu(us)", time));
             }
-            VLOG(1) << "Get props time: " << getPropsTime.elapsedInUSec() << "us";
+            VLOG(1) << "Get props time: " << time << "us";
         })
         .then([this, gv](StorageRpcResponse<GetPropResponse> &&rpcResp) {
+            SCOPED_TIMER(&execTime_);
             if (otherStats_ != nullptr) {
                 addStats(rpcResp, *otherStats_);
             }
-            SCOPED_TIMER(&execTime_);
             return handleResp(std::move(rpcResp), gv->colNamesRef());
         });
 }
diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp
index 4d8b142bc2cbcfa932000ca245035e3d522ad265..38fc5f40faed0e687303e9bb61de6cb7085a6a4c 100644
--- a/src/planner/PlanNode.cpp
+++ b/src/planner/PlanNode.cpp
@@ -154,13 +154,13 @@ const char* PlanNode::toString(PlanNode::Kind kind) {
         case Kind::kShowEdges:
             return "ShowEdges";
         case Kind::kShowTagIndexes:
-            return "kShowTagIndexes";
+            return "ShowTagIndexes";
         case Kind::kShowEdgeIndexes:
-            return "kShowEdgeIndexes";
+            return "ShowEdgeIndexes";
         case Kind::kShowTagIndexStatus:
-            return "kShowTagIndexStatus";
+            return "ShowTagIndexStatus";
         case Kind::kShowEdgeIndexStatus:
-            return "kShowEdgeIndexStatus";
+            return "ShowEdgeIndexStatus";
         case Kind::kCreateSnapshot:
             return "CreateSnapshot";
         case Kind::kDropSnapshot:
@@ -278,6 +278,17 @@ void PlanNode::addDescription(std::string key, std::string value, PlanNodeDescri
     desc->description->emplace_back(Pair{std::move(key), std::move(value)});
 }
 
+void PlanNode::readVariable(const std::string& varname) {
+    auto varPtr = qctx_->symTable()->getVar(varname);
+    readVariable(varPtr);
+}
+
+void PlanNode::readVariable(Variable* varPtr) {
+    DCHECK(varPtr != nullptr);
+    inputVars_.emplace_back(varPtr);
+    qctx_->symTable()->readBy(varPtr->name, this);
+}
+
 void PlanNode::calcCost() {
     VLOG(1) << "unimplemented cost calculation.";
 }
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 14977c19b4fdf5defc943d01a6326be9ccb9ea38..a0978a3fe879ac6df6c6c11c415ba255e6d8e06b 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -243,6 +243,8 @@ public:
 
 protected:
     static void addDescription(std::string key, std::string value, PlanNodeDescription* desc);
+    void readVariable(const std::string& varname);
+    void readVariable(Variable* varPtr);
 
     void clone(const PlanNode &node) {
         // TODO maybe shall copy cost_ and dependencies_ too
@@ -300,9 +302,7 @@ protected:
     SingleInputNode(QueryContext* qctx, Kind kind, const PlanNode* dep)
         : SingleDependencyNode(qctx, kind, dep) {
         if (dep != nullptr) {
-            auto* inputVarPtr = dep->outputVarPtr();
-            inputVars_.emplace_back(inputVarPtr);
-            qctx_->symTable()->readBy(inputVarPtr->name, this);
+            readVariable(dep->outputVarPtr());
         } else {
             inputVars_.emplace_back(nullptr);
         }
@@ -352,14 +352,10 @@ protected:
         DCHECK(right != nullptr);
 
         dependencies_.emplace_back(left);
-        auto* leftVarPtr = left->outputVarPtr();
-        inputVars_.emplace_back(leftVarPtr);
-        qctx_->symTable()->readBy(leftVarPtr->name, this);
+        readVariable(left->outputVarPtr());
 
         dependencies_.emplace_back(right);
-        auto* rightVarPtr = right->outputVarPtr();
-        inputVars_.emplace_back(rightVarPtr);
-        qctx_->symTable()->readBy(rightVarPtr->name, this);
+        readVariable(right->outputVarPtr());
     }
 };
 
diff --git a/src/planner/Planner.cpp b/src/planner/Planner.cpp
index 230736740f4794a9856d75dfdda024f1c5657b8c..30e3194b8300071be7cc5affc6fedd07de4948e8 100644
--- a/src/planner/Planner.cpp
+++ b/src/planner/Planner.cpp
@@ -11,6 +11,12 @@
 namespace nebula {
 namespace graph {
 
+std::ostream& operator<<(std::ostream& os, const SubPlan& subplan) {
+    os << "root(" << subplan.root->toString() << "): " << subplan.root->outputVar() << ", tail("
+       << subplan.tail->toString() << "): " << subplan.tail->outputVar();
+    return os;
+}
+
 StatusOr<SubPlan> Planner::toPlan(AstContext* astCtx) {
     if (astCtx == nullptr) {
         return Status::Error("AstContext nullptr.");
diff --git a/src/planner/Planner.h b/src/planner/Planner.h
index 4f1aa723affb6432b4a732d6e428ac4a04e959c6..0f0c1f5815901b129e1ddd64aac26facc60595d4 100644
--- a/src/planner/Planner.h
+++ b/src/planner/Planner.h
@@ -7,6 +7,8 @@
 #ifndef PLANNER_PLANNER_H_
 #define PLANNER_PLANNER_H_
 
+#include <ostream>
+
 #include "common/base/Base.h"
 #include "planner/PlanNode.h"
 #include "context/ast/AstContext.h"
@@ -21,8 +23,11 @@ struct SubPlan {
     PlanNode*   tail{nullptr};
 };
 
+std::ostream& operator<<(std::ostream& os, const SubPlan& subplan);
+
 using MatchFunc = std::function<bool(AstContext* astContext)>;
 using PlannerInstantiateFunc = std::function<std::unique_ptr<Planner>()>;
+
 struct MatchAndInstantiate {
     MatchAndInstantiate(MatchFunc m, PlannerInstantiateFunc p)
         : match(std::move(m)), instantiate(std::move(p)) {}
diff --git a/src/planner/Query.cpp b/src/planner/Query.cpp
index 4e80790d64a2a384ba40075c42e50b13226f95e9..4b785dc4d716ae2c23be0311760bbbff82c40c15 100644
--- a/src/planner/Query.cpp
+++ b/src/planner/Query.cpp
@@ -271,7 +271,24 @@ std::unique_ptr<PlanNodeDescription> DataCollect::explain() const {
     return desc;
 }
 
-std::unique_ptr<PlanNodeDescription> LeftJoin::explain() const {
+Join::Join(QueryContext* qctx,
+           Kind kind,
+           PlanNode* input,
+           std::pair<std::string, int64_t> leftVar,
+           std::pair<std::string, int64_t> rightVar,
+           std::vector<Expression*> hashKeys,
+           std::vector<Expression*> probeKeys)
+    : SingleDependencyNode(qctx, kind, input),
+      leftVar_(std::move(leftVar)),
+      rightVar_(std::move(rightVar)),
+      hashKeys_(std::move(hashKeys)),
+      probeKeys_(std::move(probeKeys)) {
+    inputVars_.clear();
+    readVariable(leftVar_.first);
+    readVariable(rightVar_.first);
+}
+
+std::unique_ptr<PlanNodeDescription> Join::explain() const {
     auto desc = SingleDependencyNode::explain();
     folly::dynamic inputVar = folly::dynamic::object();
     inputVar.insert("leftVar", util::toJson(leftVar_));
@@ -279,18 +296,17 @@ std::unique_ptr<PlanNodeDescription> LeftJoin::explain() const {
     addDescription("inputVar", folly::toJson(inputVar), desc.get());
     addDescription("hashKeys", folly::toJson(util::toJson(hashKeys_)), desc.get());
     addDescription("probeKeys", folly::toJson(util::toJson(probeKeys_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<PlanNodeDescription> LeftJoin::explain() const {
+    auto desc = Join::explain();
     addDescription("kind", "LeftJoin", desc.get());
     return desc;
 }
 
 std::unique_ptr<PlanNodeDescription> InnerJoin::explain() const {
-    auto desc = SingleDependencyNode::explain();
-    folly::dynamic inputVar = folly::dynamic::object();
-    inputVar.insert("leftVar", util::toJson(leftVar_));
-    inputVar.insert("rightVar", util::toJson(rightVar_));
-    addDescription("inputVar", folly::toJson(inputVar), desc.get());
-    addDescription("hashKeys", folly::toJson(util::toJson(hashKeys_)), desc.get());
-    addDescription("probeKeys", folly::toJson(util::toJson(probeKeys_)), desc.get());
+    auto desc = Join::explain();
     addDescription("kind", "InnerJoin", desc.get());
     return desc;
 }
diff --git a/src/planner/Query.h b/src/planner/Query.h
index 8d9f8b99891a971f9697d7e0c47e58a0a5881562..9a1fef308be2e2aaace26d1441d826badf8cadbb 100644
--- a/src/planner/Query.h
+++ b/src/planner/Query.h
@@ -948,10 +948,7 @@ private:
         collectKind_ = collectKind;
         inputVars_.clear();
         for (auto& var : vars) {
-            auto* inputVarPtr = qctx_->symTable()->getVar(var);
-            DCHECK(inputVarPtr != nullptr);
-            inputVars_.emplace_back(inputVarPtr);
-            qctx_->symTable()->readBy(inputVarPtr->name, this);
+            readVariable(var);
         }
     }
 
@@ -964,31 +961,6 @@ private:
 
 class Join : public SingleDependencyNode {
 public:
-    Join(QueryContext* qctx,
-         Kind kind,
-         PlanNode* input,
-         std::pair<std::string, int64_t> leftVar,
-         std::pair<std::string, int64_t> rightVar,
-         std::vector<Expression*> hashKeys,
-         std::vector<Expression*> probeKeys)
-        : SingleDependencyNode(qctx, kind, input),
-          leftVar_(std::move(leftVar)),
-          rightVar_(std::move(rightVar)),
-          hashKeys_(std::move(hashKeys)),
-          probeKeys_(std::move(probeKeys)) {
-        inputVars_.clear();
-
-        auto* leftVarPtr = qctx_->symTable()->getVar(leftVar_.first);
-        DCHECK(leftVarPtr != nullptr);
-        inputVars_.emplace_back(leftVarPtr);
-        qctx_->symTable()->readBy(leftVarPtr->name, this);
-
-        auto* rightVarPtr = qctx_->symTable()->getVar(rightVar_.first);
-        DCHECK(rightVarPtr != nullptr);
-        inputVars_.emplace_back(rightVarPtr);
-        qctx_->symTable()->readBy(rightVarPtr->name, this);
-    }
-
     const std::pair<std::string, int64_t>& leftVar() const {
         return leftVar_;
     }
@@ -1005,7 +977,17 @@ public:
         return probeKeys_;
     }
 
+    std::unique_ptr<PlanNodeDescription> explain() const override;
+
 protected:
+    Join(QueryContext* qctx,
+         Kind kind,
+         PlanNode* input,
+         std::pair<std::string, int64_t> leftVar,
+         std::pair<std::string, int64_t> rightVar,
+         std::vector<Expression*> hashKeys,
+         std::vector<Expression*> probeKeys);
+
     // var name, var version
     std::pair<std::string, int64_t>         leftVar_;
     std::pair<std::string, int64_t>         rightVar_;
@@ -1024,8 +1006,12 @@ public:
                           std::pair<std::string, int64_t> rightVar,
                           std::vector<Expression*> hashKeys,
                           std::vector<Expression*> probeKeys) {
-        return qctx->objPool()->add(
-            new LeftJoin(qctx, input, leftVar, rightVar, hashKeys, probeKeys));
+        return qctx->objPool()->add(new LeftJoin(qctx,
+                                                 input,
+                                                 std::move(leftVar),
+                                                 std::move(rightVar),
+                                                 std::move(hashKeys),
+                                                 std::move(probeKeys)));
     }
 
     std::unique_ptr<PlanNodeDescription> explain() const override;
@@ -1037,7 +1023,13 @@ private:
              std::pair<std::string, int64_t> rightVar,
              std::vector<Expression*> hashKeys,
              std::vector<Expression*> probeKeys)
-        : Join(qctx, Kind::kLeftJoin, input, leftVar, rightVar, hashKeys, probeKeys) {}
+        : Join(qctx,
+               Kind::kLeftJoin,
+               input,
+               std::move(leftVar),
+               std::move(rightVar),
+               std::move(hashKeys),
+               std::move(probeKeys)) {}
 };
 
 /*
@@ -1051,8 +1043,12 @@ public:
                            std::pair<std::string, int64_t> rightVar,
                            std::vector<Expression*> hashKeys,
                            std::vector<Expression*> probeKeys) {
-        return qctx->objPool()->add(
-            new InnerJoin(qctx, input, leftVar, rightVar, hashKeys, probeKeys));
+        return qctx->objPool()->add(new InnerJoin(qctx,
+                                                  input,
+                                                  std::move(leftVar),
+                                                  std::move(rightVar),
+                                                  std::move(hashKeys),
+                                                  std::move(probeKeys)));
     }
 
     std::unique_ptr<PlanNodeDescription> explain() const override;
@@ -1064,7 +1060,13 @@ private:
               std::pair<std::string, int64_t> rightVar,
               std::vector<Expression*> hashKeys,
               std::vector<Expression*> probeKeys)
-        : Join(qctx, Kind::kInnerJoin, input, leftVar, rightVar, hashKeys, probeKeys) {}
+        : Join(qctx,
+               Kind::kInnerJoin,
+               input,
+               std::move(leftVar),
+               std::move(rightVar),
+               std::move(hashKeys),
+               std::move(probeKeys)) {}
 };
 
 /*
diff --git a/src/planner/SequentialPlanner.cpp b/src/planner/SequentialPlanner.cpp
index 46cd953fd7d7dab975d2c0a17200ff718425020d..66827d82266f2b506390401e26eae973e0611174 100644
--- a/src/planner/SequentialPlanner.cpp
+++ b/src/planner/SequentialPlanner.cpp
@@ -32,7 +32,7 @@ StatusOr<SubPlan> SequentialPlanner::transform(AstContext* astCtx) {
     }
     subPlan.tail = seqCtx->startNode;
     NG_RETURN_IF_ERROR(validators.front()->appendPlan(subPlan.tail));
-    VLOG(1) << "root: " << subPlan.root->kind() << " tail: " << subPlan.tail->kind();
+    VLOG(1) << subPlan;
     return subPlan;
 }
 
diff --git a/src/planner/match/MatchClausePlanner.cpp b/src/planner/match/MatchClausePlanner.cpp
index 7f31a4339636a3f7fe9afbdb816092d6cccdcb4f..59a39893749676b0bffffcc214b19ac6dbb2d087 100644
--- a/src/planner/match/MatchClausePlanner.cpp
+++ b/src/planner/match/MatchClausePlanner.cpp
@@ -16,7 +16,7 @@
 #include "util/ExpressionUtils.h"
 #include "visitor/RewriteMatchLabelVisitor.h"
 
-using JoinPosStrategy = nebula::graph::InnerJoinStrategy::JoinPos;
+using JoinStrategyPos = nebula::graph::InnerJoinStrategy::JoinPos;
 
 namespace nebula {
 namespace graph {
@@ -137,7 +137,7 @@ Status MatchClausePlanner::expandFromNode(const std::vector<NodeInfo>& nodeInfos
     // Connect the left expand and right expand part.
     auto right = subplan.root;
     subplan.root = SegmentsConnector::innerJoinSegments(
-        matchClauseCtx->qctx, left, right, JoinPosStrategy::kStart, JoinPosStrategy::kStart);
+        matchClauseCtx->qctx, left, right, JoinStrategyPos::kStart, JoinStrategyPos::kStart);
     return Status::OK();
 }
 
@@ -172,7 +172,7 @@ Status MatchClausePlanner::leftExpandFromNode(const std::vector<NodeInfo>& nodeI
         inputVar = subplan.root->outputVar();
     }
 
-    VLOG(1) << "root: " << subplan.root->outputVar() << " tail: " << subplan.tail->outputVar();
+    VLOG(1) << subplan;
     auto left = subplan.root;
     NG_RETURN_IF_ERROR(MatchSolver::appendFetchVertexPlan(
         nodeInfos.front().filter,
@@ -190,7 +190,7 @@ Status MatchClausePlanner::leftExpandFromNode(const std::vector<NodeInfo>& nodeI
         subplan.root->setColNames(joinColNames);
     }
 
-    VLOG(1) << "root: " << subplan.root->outputVar() << " tail: " << subplan.tail->outputVar();
+    VLOG(1) << subplan;
     return Status::OK();
 }
 
@@ -221,7 +221,7 @@ Status MatchClausePlanner::rightExpandFromNode(const std::vector<NodeInfo>& node
         }
     }
 
-    VLOG(1) << "root: " << subplan.root->outputVar() << " tail: " << subplan.tail->outputVar();
+    VLOG(1) << subplan;
     auto left = subplan.root;
     NG_RETURN_IF_ERROR(MatchSolver::appendFetchVertexPlan(
         nodeInfos.back().filter,
@@ -238,7 +238,7 @@ Status MatchClausePlanner::rightExpandFromNode(const std::vector<NodeInfo>& node
         subplan.root->setColNames(joinColNames);
     }
 
-    VLOG(1) << "root: " << subplan.root->outputVar() << " tail: " << subplan.tail->outputVar();
+    VLOG(1) << subplan;
     return Status::OK();
 }
 
@@ -319,7 +319,7 @@ Status MatchClausePlanner::projectColumnsBySymbols(MatchClauseContext* matchClau
     project->setColNames(std::move(colNames));
 
     plan.root = MatchSolver::filtPathHasSameEdge(project, alias, qctx);
-    VLOG(1) << "root: " << plan.root->outputVar() << " tail: " << plan.tail->outputVar();
+    VLOG(1) << plan;
     return Status::OK();
 }
 
@@ -405,7 +405,7 @@ Status MatchClausePlanner::appendFilterPlan(MatchClauseContext* matchClauseCtx,
     auto plan = std::move(wherePlan).value();
     SegmentsConnector::addInput(plan.tail, subplan.root, true);
     subplan.root = plan.root;
-    VLOG(1) << "root: " << subplan.root->outputVar() << " tail: " << subplan.tail->outputVar();
+    VLOG(1) << subplan;
     return Status::OK();
 }
 }   // namespace graph
diff --git a/tests/common/csv_import.py b/tests/common/csv_import.py
index bc2965a33164b22c2096b6722920150ffd2f8da0..65dd9635e23a9cdb044f62e41694ef4c0e33a8d7 100644
--- a/tests/common/csv_import.py
+++ b/tests/common/csv_import.py
@@ -111,7 +111,7 @@ class CSVImporter:
         return f'{self._insert_stmt} {src_vid}->{dst_vid}@{rank}:({",".join(props)});'
 
     def value(self, ptype: str, col):
-        if col == "__NULL__" or col == "NULL" or col == "null" or col == "Null":
+        if type(col) == str and col.lower() in ["__null__", "null"]:
             return "NULL"
         return f'"{col}"' if ptype == 'string' else f'{col}'