From 0867cd45faf41ce20e0ee809b581ccd54391f74e Mon Sep 17 00:00:00 2001
From: jimingquan <mingquan.ji@vesoft.com>
Date: Tue, 9 Mar 2021 10:36:26 +0800
Subject: [PATCH] Add LeftJoin & change DataJoin to InnerJoin (#758)

* add LeftJoin & change DataJoin to InnerJoin

* add more test case

* rebase

* rebase master
---
 src/executor/CMakeLists.txt                   |   4 +-
 src/executor/Executor.cpp                     |  10 +-
 src/executor/query/DataJoinExecutor.cpp       | 123 ----
 src/executor/query/DataJoinExecutor.h         |  38 --
 src/executor/query/InnerJoinExecutor.cpp      |  90 +++
 src/executor/query/InnerJoinExecutor.h        |  35 ++
 src/executor/query/JoinExecutor.cpp           |  54 ++
 src/executor/query/JoinExecutor.h             |  29 +
 src/executor/query/LeftJoinExecutor.cpp       |  88 +++
 src/executor/query/LeftJoinExecutor.h         |  35 ++
 src/executor/test/CMakeLists.txt              |   2 +-
 src/executor/test/DataJoinTest.cpp            | 261 --------
 src/executor/test/JoinTest.cpp                | 567 ++++++++++++++++++
 src/planner/PlanNode.cpp                      |   6 +-
 src/planner/PlanNode.h                        |   3 +-
 src/planner/Query.cpp                         |  15 +-
 src/planner/Query.h                           | 109 ++--
 src/planner/match/InnerJoinStrategy.cpp       |   2 +-
 src/validator/GoValidator.cpp                 |   8 +-
 src/validator/test/GroupByValidatorTest.cpp   |  14 +-
 src/validator/test/MatchValidatorTest.cpp     |  24 +-
 src/validator/test/QueryValidatorTest.cpp     | 122 ++--
 src/validator/test/SymbolsTest.cpp            |   6 +-
 .../MergeGetNbrsDedupProjectRule.feature      |   4 +-
 24 files changed, 1091 insertions(+), 558 deletions(-)
 delete mode 100644 src/executor/query/DataJoinExecutor.cpp
 delete mode 100644 src/executor/query/DataJoinExecutor.h
 create mode 100644 src/executor/query/InnerJoinExecutor.cpp
 create mode 100644 src/executor/query/InnerJoinExecutor.h
 create mode 100644 src/executor/query/JoinExecutor.cpp
 create mode 100644 src/executor/query/JoinExecutor.h
 create mode 100644 src/executor/query/LeftJoinExecutor.cpp
 create mode 100644 src/executor/query/LeftJoinExecutor.h
 delete mode 100644 src/executor/test/DataJoinTest.cpp
 create mode 100644 src/executor/test/JoinTest.cpp

diff --git a/src/executor/CMakeLists.txt b/src/executor/CMakeLists.txt
index cd8d3bcd..719cc46c 100644
--- a/src/executor/CMakeLists.txt
+++ b/src/executor/CMakeLists.txt
@@ -28,7 +28,9 @@ nebula_add_library(
     query/UnionExecutor.cpp
     query/UnionAllVersionVarExecutor.cpp
     query/DataCollectExecutor.cpp
-    query/DataJoinExecutor.cpp
+    query/JoinExecutor.cpp
+    query/LeftJoinExecutor.cpp
+    query/InnerJoinExecutor.cpp
     query/IndexScanExecutor.cpp
     query/AssignExecutor.cpp
     algo/ConjunctPathExecutor.cpp
diff --git a/src/executor/Executor.cpp b/src/executor/Executor.cpp
index 6905e261..c0783770 100644
--- a/src/executor/Executor.cpp
+++ b/src/executor/Executor.cpp
@@ -63,7 +63,8 @@
 #include "executor/mutate/UpdateExecutor.h"
 #include "executor/query/AggregateExecutor.h"
 #include "executor/query/DataCollectExecutor.h"
-#include "executor/query/DataJoinExecutor.h"
+#include "executor/query/LeftJoinExecutor.h"
+#include "executor/query/InnerJoinExecutor.h"
 #include "executor/query/DedupExecutor.h"
 #include "executor/query/FilterExecutor.h"
 #include "executor/query/GetEdgesExecutor.h"
@@ -327,8 +328,11 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) {
         case PlanNode::Kind::kShowSnapshots: {
             return pool->add(new ShowSnapshotsExecutor(node, qctx));
         }
-        case PlanNode::Kind::kDataJoin: {
-            return pool->add(new DataJoinExecutor(node, qctx));
+        case PlanNode::Kind::kLeftJoin: {
+            return pool->add(new LeftJoinExecutor(node, qctx));
+        }
+        case PlanNode::Kind::kInnerJoin: {
+            return pool->add(new InnerJoinExecutor(node, qctx));
         }
         case PlanNode::Kind::kDeleteVertices: {
             return pool->add(new DeleteVerticesExecutor(node, qctx));
diff --git a/src/executor/query/DataJoinExecutor.cpp b/src/executor/query/DataJoinExecutor.cpp
deleted file mode 100644
index cad741bf..00000000
--- a/src/executor/query/DataJoinExecutor.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/* Copyright (c) 2020 vesoft inc. All rights reserved.
- *
- * This source code is licensed under Apache 2.0 License,
- * attached with Common Clause Condition 1.0, found in the LICENSES directory.
- */
-
-#include "executor/query/DataJoinExecutor.h"
-
-#include "planner/Query.h"
-#include "context/QueryExpressionContext.h"
-#include "context/Iterator.h"
-#include "util/ScopedTimer.h"
-
-namespace nebula {
-namespace graph {
-folly::Future<Status> DataJoinExecutor::execute() {
-    return doInnerJoin();
-}
-
-Status DataJoinExecutor::close() {
-    exchange_ = false;
-    hashTable_.clear();
-    return Executor::close();
-}
-
-folly::Future<Status> DataJoinExecutor::doInnerJoin() {
-    SCOPED_TIMER(&execTime_);
-
-    auto* dataJoin = asNode<DataJoin>(node());
-    auto colNames = dataJoin->colNames();
-    auto lhsIter = ectx_
-                       ->getVersionedResult(dataJoin->leftVar().first,
-                                            dataJoin->leftVar().second)
-                       .iter();
-    DCHECK(!!lhsIter);
-    if (lhsIter->isGetNeighborsIter() || lhsIter->isDefaultIter()) {
-        std::stringstream ss;
-        ss << "Join executor does not support " << lhsIter->kind();
-        return error(Status::Error(ss.str()));
-    }
-    auto rhsIter = ectx_
-                       ->getVersionedResult(dataJoin->rightVar().first,
-                                            dataJoin->rightVar().second)
-                       .iter();
-    DCHECK(!!rhsIter);
-    if (lhsIter->isGetNeighborsIter() || lhsIter->isDefaultIter()) {
-        std::stringstream ss;
-        ss << "Join executor does not support " << lhsIter->kind();
-        return error(Status::Error(ss.str()));
-    }
-
-    auto resultIter = std::make_unique<JoinIter>(std::move(colNames));
-    resultIter->joinIndex(lhsIter.get(), rhsIter.get());
-    auto bucketSize =
-        lhsIter->size() > rhsIter->size() ? rhsIter->size() : lhsIter->size();
-    hashTable_.reserve(bucketSize);
-    resultIter->reserve(lhsIter->size() > rhsIter->size() ? lhsIter->size() : rhsIter->size());
-
-    if (!(lhsIter->empty() || rhsIter->empty())) {
-        if (lhsIter->size() < rhsIter->size()) {
-            buildHashTable(dataJoin->hashKeys(), lhsIter.get());
-            probe(dataJoin->probeKeys(), rhsIter.get(), resultIter.get());
-        } else {
-            exchange_ = true;
-            buildHashTable(dataJoin->probeKeys(), rhsIter.get());
-            probe(dataJoin->hashKeys(), lhsIter.get(), resultIter.get());
-        }
-    }
-    return finish(ResultBuilder().iter(std::move(resultIter)).finish());
-}
-
-void DataJoinExecutor::buildHashTable(const std::vector<Expression*>& hashKeys,
-                                      Iterator* iter) {
-    QueryExpressionContext ctx(ectx_);
-    for (; iter->valid(); iter->next()) {
-        List list;
-        list.values.reserve(hashKeys.size());
-        for (auto& col : hashKeys) {
-            Value val = col->eval(ctx(iter));
-            list.values.emplace_back(std::move(val));
-        }
-
-        auto& vals = hashTable_[list];
-        vals.emplace_back(iter->row());
-    }
-}
-
-void DataJoinExecutor::probe(const std::vector<Expression*>& probeKeys,
-                             Iterator* probeIter, JoinIter* resultIter) {
-    QueryExpressionContext ctx(ectx_);
-    for (; probeIter->valid(); probeIter->next()) {
-        List list;
-        list.values.reserve(probeKeys.size());
-        for (auto& col : probeKeys) {
-            Value val = col->eval(ctx(probeIter));
-            list.values.emplace_back(std::move(val));
-        }
-
-        const auto& range = hashTable_.find(list);
-        if (range == hashTable_.end()) {
-            continue;
-        }
-        for (auto* row : range->second) {
-            std::vector<const Row*> values;
-            auto& lSegs = row->segments();
-            auto& rSegs = probeIter->row()->segments();
-            values.reserve(lSegs.size() + rSegs.size());
-            if (exchange_) {
-                values.insert(values.end(), rSegs.begin(), rSegs.end());
-                values.insert(values.end(), lSegs.begin(), lSegs.end());
-            } else {
-                values.insert(values.end(), lSegs.begin(), lSegs.end());
-                values.insert(values.end(), rSegs.begin(), rSegs.end());
-            }
-            size_t size = row->size() + probeIter->row()->size();
-            JoinIter::JoinLogicalRow newRow(std::move(values), size,
-                                        &resultIter->getColIdxIndices());
-            resultIter->addRow(std::move(newRow));
-        }
-    }
-}
-}  // namespace graph
-}  // namespace nebula
diff --git a/src/executor/query/DataJoinExecutor.h b/src/executor/query/DataJoinExecutor.h
deleted file mode 100644
index 1332dbd1..00000000
--- a/src/executor/query/DataJoinExecutor.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* Copyright (c) 2020 vesoft inc. All rights reserved.
- *
- * This source code is licensed under Apache 2.0 License,
- * attached with Common Clause Condition 1.0, found in the LICENSES directory.
- */
-
-#ifndef EXECUTOR_QUERY_DATAJOINEXECUTOR_H_
-#define EXECUTOR_QUERY_DATAJOINEXECUTOR_H_
-
-#include "executor/Executor.h"
-
-namespace nebula {
-namespace graph {
-
-class DataJoinExecutor final : public Executor {
-public:
-    DataJoinExecutor(const PlanNode *node, QueryContext *qctx)
-        : Executor("DataJoinExecutor", node, qctx) {}
-
-    folly::Future<Status> execute() override;
-
-    Status close() override;
-
-private:
-    folly::Future<Status> doInnerJoin();
-
-    void buildHashTable(const std::vector<Expression*>& hashKeys, Iterator* iter);
-
-    void probe(const std::vector<Expression*>& probeKeys, Iterator* probeiter,
-               JoinIter* resultIter);
-
-private:
-    bool                                                      exchange_{false};
-    std::unordered_map<List, std::vector<const LogicalRow*>>  hashTable_;
-};
-}  // namespace graph
-}  // namespace nebula
-#endif
diff --git a/src/executor/query/InnerJoinExecutor.cpp b/src/executor/query/InnerJoinExecutor.cpp
new file mode 100644
index 00000000..429afce3
--- /dev/null
+++ b/src/executor/query/InnerJoinExecutor.cpp
@@ -0,0 +1,90 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "executor/query/InnerJoinExecutor.h"
+
+#include "context/Iterator.h"
+#include "context/QueryExpressionContext.h"
+#include "planner/Query.h"
+#include "util/ScopedTimer.h"
+
+namespace nebula {
+namespace graph {
+folly::Future<Status> InnerJoinExecutor::execute() {
+    SCOPED_TIMER(&execTime_);
+    NG_RETURN_IF_ERROR(checkInputDataSets());
+    return join();
+}
+
+Status InnerJoinExecutor::close() {
+    exchange_ = false;
+    hashTable_.clear();
+    return Executor::close();
+}
+
+folly::Future<Status> InnerJoinExecutor::join() {
+    auto* join = asNode<Join>(node());
+    auto lhsIter = ectx_->getVersionedResult(join->leftVar().first, join->leftVar().second).iter();
+    auto rhsIter =
+        ectx_->getVersionedResult(join->rightVar().first, join->rightVar().second).iter();
+
+    auto resultIter = std::make_unique<JoinIter>(join->colNames());
+    resultIter->joinIndex(lhsIter.get(), rhsIter.get());
+    auto bucketSize = lhsIter->size() > rhsIter->size() ? rhsIter->size() : lhsIter->size();
+    hashTable_.reserve(bucketSize);
+    resultIter->reserve(lhsIter->size() > rhsIter->size() ? lhsIter->size() : rhsIter->size());
+
+    if (!(lhsIter->empty() || rhsIter->empty())) {
+        if (lhsIter->size() < rhsIter->size()) {
+            buildHashTable(join->hashKeys(), lhsIter.get());
+            probe(join->probeKeys(), rhsIter.get(), resultIter.get());
+        } else {
+            exchange_ = true;
+            buildHashTable(join->probeKeys(), rhsIter.get());
+            probe(join->hashKeys(), lhsIter.get(), resultIter.get());
+        }
+    }
+    return finish(ResultBuilder().iter(std::move(resultIter)).finish());
+}
+
+void InnerJoinExecutor::probe(const std::vector<Expression*>& probeKeys,
+                              Iterator* probeIter,
+                              JoinIter* resultIter) {
+    QueryExpressionContext ctx(ectx_);
+    for (; probeIter->valid(); probeIter->next()) {
+        List list;
+        list.values.reserve(probeKeys.size());
+        for (auto& col : probeKeys) {
+            Value val = col->eval(ctx(probeIter));
+            list.values.emplace_back(std::move(val));
+        }
+
+        auto range = hashTable_.find(list);
+        if (range == hashTable_.end()) {
+            continue;
+        }
+        for (auto* row : range->second) {
+            std::vector<const Row*> values;
+            auto& lSegs = row->segments();
+            auto& rSegs = probeIter->row()->segments();
+            values.reserve(lSegs.size() + rSegs.size());
+            if (exchange_) {
+                values.insert(values.end(), rSegs.begin(), rSegs.end());
+                values.insert(values.end(), lSegs.begin(), lSegs.end());
+            } else {
+                values.insert(values.end(), lSegs.begin(), lSegs.end());
+                values.insert(values.end(), rSegs.begin(), rSegs.end());
+            }
+            size_t size = row->size() + probeIter->row()->size();
+            JoinIter::JoinLogicalRow newRow(
+                std::move(values), size, &resultIter->getColIdxIndices());
+            VLOG(1) << node()->outputVar() << " : " << newRow;
+            resultIter->addRow(std::move(newRow));
+        }
+    }
+}
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/executor/query/InnerJoinExecutor.h b/src/executor/query/InnerJoinExecutor.h
new file mode 100644
index 00000000..8651ca34
--- /dev/null
+++ b/src/executor/query/InnerJoinExecutor.h
@@ -0,0 +1,35 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef EXECUTOR_QUERY_INNERJOINEXECUTOR_H_
+#define EXECUTOR_QUERY_INNERJOINEXECUTOR_H_
+
+#include "executor/query/JoinExecutor.h"
+
+namespace nebula {
+namespace graph {
+
+class InnerJoinExecutor final : public JoinExecutor {
+public:
+    InnerJoinExecutor(const PlanNode* node, QueryContext* qctx)
+        : JoinExecutor("InnerJoinExecutor", node, qctx) {}
+
+    folly::Future<Status> execute() override;
+
+    Status close() override;
+
+private:
+    folly::Future<Status> join();
+    void probe(const std::vector<Expression*>& probeKeys,
+               Iterator* probeiter,
+               JoinIter* resultIter);
+
+private:
+    bool exchange_{false};
+};
+}  // namespace graph
+}  // namespace nebula
+#endif
diff --git a/src/executor/query/JoinExecutor.cpp b/src/executor/query/JoinExecutor.cpp
new file mode 100644
index 00000000..4395f4a9
--- /dev/null
+++ b/src/executor/query/JoinExecutor.cpp
@@ -0,0 +1,54 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "executor/query/JoinExecutor.h"
+
+#include "planner/Query.h"
+#include "context/QueryExpressionContext.h"
+#include "context/Iterator.h"
+
+namespace nebula {
+namespace graph {
+
+Status JoinExecutor::checkInputDataSets() {
+    auto* join = asNode<Join>(node());
+    auto lhsIter = ectx_->getVersionedResult(join->leftVar().first, join->leftVar().second).iter();
+    DCHECK(!!lhsIter);
+    VLOG(1) << "lhs: " << join->leftVar().first << " " << lhsIter->size();
+    if (lhsIter->isGetNeighborsIter() || lhsIter->isDefaultIter()) {
+        std::stringstream ss;
+        ss << "Join executor does not support " << lhsIter->kind();
+        return Status::Error(ss.str());
+    }
+    auto rhsIter =
+        ectx_->getVersionedResult(join->rightVar().first, join->rightVar().second).iter();
+    DCHECK(!!rhsIter);
+    VLOG(1) << "rhs: " << join->rightVar().first << " " << rhsIter->size();
+    if (rhsIter->isGetNeighborsIter() || rhsIter->isDefaultIter()) {
+        std::stringstream ss;
+        ss << "Join executor does not support " << rhsIter->kind();
+        return Status::Error(ss.str());
+    }
+    return Status::OK();
+}
+
+void JoinExecutor::buildHashTable(const std::vector<Expression*>& hashKeys, Iterator* iter) {
+    QueryExpressionContext ctx(ectx_);
+    for (; iter->valid(); iter->next()) {
+        List list;
+        list.values.reserve(hashKeys.size());
+        for (auto& col : hashKeys) {
+            Value val = col->eval(ctx(iter));
+            list.values.emplace_back(std::move(val));
+        }
+
+        auto& vals = hashTable_[list];
+        vals.emplace_back(iter->row());
+    }
+}
+
+}  // namespace graph
+}  // namespace nebula
diff --git a/src/executor/query/JoinExecutor.h b/src/executor/query/JoinExecutor.h
new file mode 100644
index 00000000..33e0eb9f
--- /dev/null
+++ b/src/executor/query/JoinExecutor.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef EXECUTOR_QUERY_JOINEXECUTOR_H_
+#define EXECUTOR_QUERY_JOINEXECUTOR_H_
+
+#include "executor/Executor.h"
+
+namespace nebula {
+namespace graph {
+
+class JoinExecutor : public Executor {
+public:
+    JoinExecutor(const std::string& name, const PlanNode* node, QueryContext* qctx)
+        : Executor(name, node, qctx) {}
+
+    Status checkInputDataSets();
+
+    void buildHashTable(const std::vector<Expression*>& hashKeys, Iterator* iter);
+
+protected:
+    std::unordered_map<List, std::vector<const LogicalRow*>>  hashTable_;
+};
+}  // namespace graph
+}  // namespace nebula
+#endif
diff --git a/src/executor/query/LeftJoinExecutor.cpp b/src/executor/query/LeftJoinExecutor.cpp
new file mode 100644
index 00000000..ed521a93
--- /dev/null
+++ b/src/executor/query/LeftJoinExecutor.cpp
@@ -0,0 +1,88 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "executor/query/LeftJoinExecutor.h"
+
+#include "context/Iterator.h"
+#include "context/QueryExpressionContext.h"
+#include "planner/Query.h"
+#include "util/ScopedTimer.h"
+
+namespace nebula {
+namespace graph {
+folly::Future<Status> LeftJoinExecutor::execute() {
+    SCOPED_TIMER(&execTime_);
+    NG_RETURN_IF_ERROR(checkInputDataSets());
+    return join();
+}
+
+Status LeftJoinExecutor::close() {
+    hashTable_.clear();
+    return Executor::close();
+}
+
+folly::Future<Status> LeftJoinExecutor::join() {
+    auto* join = asNode<Join>(node());
+    auto lhsIter = ectx_->getVersionedResult(join->leftVar().first, join->leftVar().second).iter();
+    auto& rhsResult = ectx_->getVersionedResult(join->rightVar().first, join->rightVar().second);
+    rightColSize_ = rhsResult.valuePtr()->getDataSet().colNames.size();
+    auto rhsIter = rhsResult.iter();
+
+    auto resultIter = std::make_unique<JoinIter>(join->colNames());
+    resultIter->joinIndex(lhsIter.get(), rhsIter.get());
+    hashTable_.reserve(rhsIter->size() == 0 ? 1 : rhsIter->size());
+    resultIter->reserve(lhsIter->size());
+    if (!lhsIter->empty()) {
+        buildHashTable(join->probeKeys(), rhsIter.get());
+        probe(join->hashKeys(), lhsIter.get(), resultIter.get());
+    }
+    return finish(ResultBuilder().iter(std::move(resultIter)).finish());
+}
+
+void LeftJoinExecutor::probe(const std::vector<Expression*>& probeKeys,
+                             Iterator* probeIter,
+                             JoinIter* resultIter) {
+    QueryExpressionContext ctx(ectx_);
+    for (; probeIter->valid(); probeIter->next()) {
+        List list;
+        list.values.reserve(probeKeys.size());
+        for (auto& col : probeKeys) {
+            Value val = col->eval(ctx(probeIter));
+            list.values.emplace_back(std::move(val));
+        }
+
+        auto range = hashTable_.find(list);
+        if (range == hashTable_.end()) {
+            std::vector<const Row*> values;
+            auto& lSegs = probeIter->row()->segments();
+            values.reserve(lSegs.size() + 1);
+            values.insert(values.end(), lSegs.begin(), lSegs.end());
+            Row* emptyRow = qctx_->objPool()->add(new List(std::vector<Value>(rightColSize_)));
+            values.insert(values.end(), emptyRow);
+            size_t size = probeIter->row()->size() + rightColSize_;
+            JoinIter::JoinLogicalRow newRow(
+                std::move(values), size, &resultIter->getColIdxIndices());
+            VLOG(1) << node()->outputVar() << " : " << newRow;
+            resultIter->addRow(std::move(newRow));
+        } else {
+            for (auto* row : range->second) {
+                std::vector<const Row*> values;
+                auto& lSegs = probeIter->row()->segments();
+                auto& rSegs = row->segments();
+                values.reserve(lSegs.size() + rSegs.size());
+                values.insert(values.end(), lSegs.begin(), lSegs.end());
+                values.insert(values.end(), rSegs.begin(), rSegs.end());
+                size_t size = row->size() + probeIter->row()->size();
+                JoinIter::JoinLogicalRow newRow(
+                    std::move(values), size, &resultIter->getColIdxIndices());
+                VLOG(1) << node()->outputVar() << " : " << newRow;
+                resultIter->addRow(std::move(newRow));
+            }
+        }
+    }
+}
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/executor/query/LeftJoinExecutor.h b/src/executor/query/LeftJoinExecutor.h
new file mode 100644
index 00000000..c4245e34
--- /dev/null
+++ b/src/executor/query/LeftJoinExecutor.h
@@ -0,0 +1,35 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef EXECUTOR_QUERY_LEFTJOINEXECUTOR_H_
+#define EXECUTOR_QUERY_LEFTJOINEXECUTOR_H_
+
+#include "executor/query/JoinExecutor.h"
+
+namespace nebula {
+namespace graph {
+
+class LeftJoinExecutor final : public JoinExecutor {
+public:
+    LeftJoinExecutor(const PlanNode *node, QueryContext *qctx)
+        : JoinExecutor("LeftJoinExecutor", node, qctx) {}
+
+    folly::Future<Status> execute() override;
+
+    Status close() override;
+
+private:
+    folly::Future<Status> join();
+
+    void probe(const std::vector<Expression*>& probeKeys, Iterator* probeiter,
+               JoinIter* resultIter);
+
+private:
+    size_t rightColSize_{0};
+};
+}  // namespace graph
+}  // namespace nebula
+#endif
diff --git a/src/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt
index 8eedd7e6..789a5f71 100644
--- a/src/executor/test/CMakeLists.txt
+++ b/src/executor/test/CMakeLists.txt
@@ -72,7 +72,7 @@ nebula_add_test(
         SortTest.cpp
         TopNTest.cpp
         AggregateTest.cpp
-        DataJoinTest.cpp
+        JoinTest.cpp
         BFSShortestTest.cpp
         ConjunctPathTest.cpp
         ProduceSemiShortestPathTest.cpp
diff --git a/src/executor/test/DataJoinTest.cpp b/src/executor/test/DataJoinTest.cpp
deleted file mode 100644
index 090144af..00000000
--- a/src/executor/test/DataJoinTest.cpp
+++ /dev/null
@@ -1,261 +0,0 @@
-/* Copyright (c) 2020 vesoft inc. All rights reserved.
- *
- * This source code is licensed under Apache 2.0 License,
- * attached with Common Clause Condition 1.0, found in the LICENSES directory.
- */
-
-#include <gtest/gtest.h>
-
-#include "context/QueryContext.h"
-#include "planner/Query.h"
-#include "executor/query/DataJoinExecutor.h"
-#include "executor/test/QueryTestBase.h"
-
-namespace nebula {
-namespace graph {
-class DataJoinTest : public QueryTestBase {
-protected:
-    void SetUp() override {
-        qctx_ = std::make_unique<QueryContext>();
-        {
-            DataSet ds;
-            ds.colNames = {kVid, "tag_prop", "edge_prop", kDst};
-            for (auto i = 0; i < 10; ++i) {
-                Row row;
-                // _vid
-                row.values.emplace_back(folly::to<std::string>(i / 2));
-                row.values.emplace_back(i);
-                row.values.emplace_back(i + 1);
-                // _dst
-                row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
-                ds.rows.emplace_back(std::move(row));
-            }
-            qctx_->symTable()->newVariable("var1");
-            qctx_->ectx()->setResult(
-                "var1",
-                ResultBuilder().value(Value(std::move(ds))).finish());
-        }
-        {
-            DataSet ds;
-            ds.colNames = {"src", "dst"};
-            for (auto i = 0; i < 5; ++i) {
-                Row row;
-                row.values.emplace_back(folly::to<std::string>(i + 11));
-                row.values.emplace_back(folly::to<std::string>(i));
-                ds.rows.emplace_back(std::move(row));
-            }
-            qctx_->symTable()->newVariable("var2");
-            qctx_->ectx()->setResult(
-                "var2", ResultBuilder().value(Value(std::move(ds))).finish());
-        }
-        {
-            DataSet ds;
-            ds.colNames = {"col1"};
-            Row row;
-            row.values.emplace_back("11");
-            ds.rows.emplace_back(std::move(row));
-            qctx_->symTable()->newVariable("var3");
-            qctx_->ectx()->setResult(
-                "var3", ResultBuilder().value(Value(std::move(ds))).finish());
-        }
-        {
-            DataSet ds;
-            ds.colNames = {kVid, "tag_prop", "edge_prop", kDst};
-            qctx_->symTable()->newVariable("empty_var1");
-            qctx_->ectx()->setResult(
-                "empty_var1",
-                ResultBuilder().value(Value(std::move(ds))).finish());
-        }
-        {
-            DataSet ds;
-            ds.colNames = {"src", "dst"};
-            qctx_->symTable()->newVariable("empty_var2");
-            qctx_->ectx()->setResult(
-                "empty_var2",
-                ResultBuilder().value(Value(std::move(ds))).finish());
-        }
-    }
-
-    void testJoin(std::string left, std::string right, DataSet& expected, int64_t line);
-
-protected:
-    std::unique_ptr<QueryContext> qctx_;
-};
-
-void DataJoinTest::testJoin(std::string left, std::string right,
-                            DataSet& expected, int64_t line) {
-    VariablePropertyExpression key(new std::string(left),
-                                   new std::string("dst"));
-    std::vector<Expression*> hashKeys = {&key};
-    VariablePropertyExpression probe(new std::string(right),
-                                     new std::string("_vid"));
-    std::vector<Expression*> probeKeys = {&probe};
-
-    auto* dataJoin =
-        DataJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
-                       std::move(probeKeys));
-    dataJoin->setColNames(std::vector<std::string>{
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst});
-
-    auto dataJoinExe =
-        std::make_unique<DataJoinExecutor>(dataJoin, qctx_.get());
-    auto future = dataJoinExe->execute();
-    auto status = std::move(future).get();
-    EXPECT_TRUE(status.ok()) << "LINE: " << line;
-    auto& result = qctx_->ectx()->getResult(dataJoin->outputVar());
-
-    DataSet resultDs;
-    resultDs.colNames = {
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
-    auto iter = result.iter();
-    for (; iter->valid(); iter->next()) {
-        const auto& cols = *iter->row();
-        Row row;
-        for (size_t i = 0; i < cols.size(); ++i) {
-            Value col = cols[i];
-            row.values.emplace_back(std::move(col));
-        }
-        resultDs.rows.emplace_back(std::move(row));
-    }
-
-    EXPECT_EQ(resultDs, expected) << "LINE: " << line;
-    EXPECT_EQ(result.state(), Result::State::kSuccess) << "LINE: " << line;
-}
-
-TEST_F(DataJoinTest, Join) {
-    DataSet expected;
-    expected.colNames = {
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
-    for (auto i = 11; i < 16; ++i) {
-        Row row1;
-        row1.values.emplace_back(folly::to<std::string>(i));
-        row1.values.emplace_back(folly::to<std::string>(i % 11));
-        row1.values.emplace_back(folly::to<std::string>(i % 11));
-        row1.values.emplace_back(i % 11 * 2);
-        row1.values.emplace_back(i % 11 * 2 + 1);
-        row1.values.emplace_back(folly::to<std::string>(i - 6));
-        expected.rows.emplace_back(std::move(row1));
-
-        Row row2;
-        row2.values.emplace_back(folly::to<std::string>(i));
-        row2.values.emplace_back(folly::to<std::string>(i % 11));
-        row2.values.emplace_back(folly::to<std::string>(i % 11));
-        row2.values.emplace_back(i % 11 * 2 + 1);
-        row2.values.emplace_back(i % 11 * 2 + 2);
-        row2.values.emplace_back(folly::to<std::string>(i - 5));
-        expected.rows.emplace_back(std::move(row2));
-    }
-
-    // $var1 inner join $var2 on $var2.dst = $var1._vid
-    testJoin("var2", "var1", expected, __LINE__);
-}
-
-TEST_F(DataJoinTest, JoinTwice) {
-    std::string join;
-    {
-        std::string left = "var2";
-        std::string right = "var1";
-        VariablePropertyExpression key(new std::string(left),
-                                    new std::string("dst"));
-        std::vector<Expression*> hashKeys = {&key};
-        VariablePropertyExpression probe(new std::string(right),
-                                        new std::string("_vid"));
-        std::vector<Expression*> probeKeys = {&probe};
-
-        auto* dataJoin =
-            DataJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
-                        std::move(probeKeys));
-        dataJoin->setColNames(std::vector<std::string>{
-            "src", "dst", kVid, "tag_prop", "edge_prop", kDst});
-
-        auto dataJoinExe =
-            std::make_unique<DataJoinExecutor>(dataJoin, qctx_.get());
-        auto future = dataJoinExe->execute();
-
-        join = dataJoin->outputVar();
-    }
-
-    std::string left = join;
-    std::string right = "var3";
-    VariablePropertyExpression key(new std::string(left),
-                                new std::string("src"));
-    std::vector<Expression*> hashKeys = {&key};
-    VariablePropertyExpression probe(new std::string(right),
-                                    new std::string("col1"));
-    std::vector<Expression*> probeKeys = {&probe};
-
-    auto* dataJoin =
-        DataJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
-                    std::move(probeKeys));
-    dataJoin->setColNames(std::vector<std::string>{
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"});
-
-    auto dataJoinExe =
-        std::make_unique<DataJoinExecutor>(dataJoin, qctx_.get());
-    auto future = dataJoinExe->execute();
-    auto status = std::move(future).get();
-    EXPECT_TRUE(status.ok());
-    auto& result = qctx_->ectx()->getResult(dataJoin->outputVar());
-
-    DataSet resultDs;
-    resultDs.colNames = {
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"};
-    auto iter = result.iter();
-    for (; iter->valid(); iter->next()) {
-        const auto& cols = *iter->row();
-        Row row;
-        for (size_t i = 0; i < cols.size(); ++i) {
-            Value col = cols[i];
-            row.values.emplace_back(std::move(col));
-        }
-        resultDs.rows.emplace_back(std::move(row));
-    }
-
-    DataSet expected;
-    expected.colNames = {
-        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"};
-    Row row1;
-    row1.values.emplace_back("11");
-    row1.values.emplace_back("0");
-    row1.values.emplace_back("0");
-    row1.values.emplace_back(0);
-    row1.values.emplace_back(1);
-    row1.values.emplace_back("5");
-    row1.values.emplace_back("11");
-    expected.rows.emplace_back(row1);
-    row1.values[3] = 1;
-    row1.values[4] = 2;
-    row1.values[5] = "6";
-    expected.rows.emplace_back(std::move(row1));
-
-    EXPECT_EQ(resultDs, expected);
-    EXPECT_EQ(result.state(), Result::State::kSuccess);
-}
-
-TEST_F(DataJoinTest, JoinEmpty) {
-    {
-        DataSet expected;
-        expected.colNames = {
-            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
-
-        testJoin("var2", "empty_var1", expected, __LINE__);
-    }
-
-    {
-        DataSet expected;
-        expected.colNames = {
-            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
-
-        testJoin("empty_var2", "var1", expected, __LINE__);
-    }
-
-    {
-        DataSet expected;
-        expected.colNames = {
-            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
-
-        testJoin("empty_var2", "empty_var1", expected, __LINE__);
-    }
-}
-}  // namespace graph
-}  // namespace nebula
diff --git a/src/executor/test/JoinTest.cpp b/src/executor/test/JoinTest.cpp
new file mode 100644
index 00000000..af874353
--- /dev/null
+++ b/src/executor/test/JoinTest.cpp
@@ -0,0 +1,567 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include <gtest/gtest.h>
+
+#include "context/QueryContext.h"
+#include "planner/Query.h"
+#include "executor/query/InnerJoinExecutor.h"
+#include "executor/query/LeftJoinExecutor.h"
+#include "executor/test/QueryTestBase.h"
+
+namespace nebula {
+namespace graph {
+class JoinTest : public QueryTestBase {
+protected:
+    void SetUp() override {
+        qctx_ = std::make_unique<QueryContext>();
+        {
+            DataSet ds;
+            ds.colNames = {kVid, "tag_prop", "edge_prop", kDst};
+            for (auto i = 0; i < 10; ++i) {
+                Row row;
+                // _vid
+                row.values.emplace_back(folly::to<std::string>(i / 2));
+                row.values.emplace_back(i);
+                row.values.emplace_back(i + 1);
+                // _dst
+                row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+                ds.rows.emplace_back(std::move(row));
+            }
+            qctx_->symTable()->newVariable("var1");
+            qctx_->ectx()->setResult(
+                "var1",
+                ResultBuilder().value(Value(std::move(ds))).finish());
+        }
+        {
+            DataSet ds;
+            ds.colNames = {"src", "dst"};
+            for (auto i = 0; i < 5; ++i) {
+                Row row;
+                row.values.emplace_back(folly::to<std::string>(i + 11));
+                row.values.emplace_back(folly::to<std::string>(i));
+                ds.rows.emplace_back(std::move(row));
+            }
+            qctx_->symTable()->newVariable("var2");
+            qctx_->ectx()->setResult(
+                "var2", ResultBuilder().value(Value(std::move(ds))).finish());
+        }
+        {
+            DataSet ds;
+            ds.colNames = {"col1"};
+            Row row;
+            row.values.emplace_back("11");
+            ds.rows.emplace_back(std::move(row));
+            qctx_->symTable()->newVariable("var3");
+            qctx_->ectx()->setResult(
+                "var3", ResultBuilder().value(Value(std::move(ds))).finish());
+        }
+        {
+            DataSet ds;
+            ds.colNames = {kVid, "tag_prop", "edge_prop", kDst};
+            qctx_->symTable()->newVariable("empty_var1");
+            qctx_->ectx()->setResult(
+                "empty_var1",
+                ResultBuilder().value(Value(std::move(ds))).finish());
+        }
+        {
+            DataSet ds;
+            ds.colNames = {"src", "dst"};
+            qctx_->symTable()->newVariable("empty_var2");
+            qctx_->ectx()->setResult(
+                "empty_var2",
+                ResultBuilder().value(Value(std::move(ds))).finish());
+        }
+    }
+
+    void testInnerJoin(std::string left, std::string right, DataSet& expected, int64_t line);
+    void testLeftJoin(std::string left, std::string right, DataSet& expected, int64_t line);
+
+protected:
+    std::unique_ptr<QueryContext> qctx_;
+};
+
+void JoinTest::testInnerJoin(std::string left, std::string right,
+                            DataSet& expected, int64_t line) {
+    VariablePropertyExpression key(new std::string(left),
+                                   new std::string("dst"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right),
+                                     new std::string("_vid"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join =
+        InnerJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
+                       std::move(probeKeys));
+    join->setColNames(std::vector<std::string>{
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst});
+
+    auto joinExe =
+        std::make_unique<InnerJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok()) << "LINE: " << line;
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    EXPECT_EQ(resultDs, expected) << "LINE: " << line;
+    EXPECT_EQ(result.state(), Result::State::kSuccess) << "LINE: " << line;
+}
+
+void JoinTest::testLeftJoin(std::string left, std::string right,
+                            DataSet& expected, int64_t line) {
+    VariablePropertyExpression key(new std::string(left),
+                                   new std::string("_vid"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right),
+                                     new std::string("dst"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join =
+        LeftJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
+                       std::move(probeKeys));
+    join->setColNames(std::vector<std::string>{
+        kVid, "tag_prop", "edge_prop", kDst, "src", "dst"});
+
+    auto joinExe =
+        std::make_unique<LeftJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok()) << "LINE: " << line;
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {
+        kVid, "tag_prop", "edge_prop", kDst, "src", "dst"};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    EXPECT_EQ(resultDs, expected) << "LINE: " << line;
+    EXPECT_EQ(result.state(), Result::State::kSuccess) << "LINE: " << line;
+}
+
+TEST_F(JoinTest, InnerJoin) {
+    DataSet expected;
+    expected.colNames = {
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
+    for (auto i = 11; i < 16; ++i) {
+        Row row1;
+        row1.values.emplace_back(folly::to<std::string>(i));
+        row1.values.emplace_back(folly::to<std::string>(i % 11));
+        row1.values.emplace_back(folly::to<std::string>(i % 11));
+        row1.values.emplace_back(i % 11 * 2);
+        row1.values.emplace_back(i % 11 * 2 + 1);
+        row1.values.emplace_back(folly::to<std::string>(i - 6));
+        expected.rows.emplace_back(std::move(row1));
+
+        Row row2;
+        row2.values.emplace_back(folly::to<std::string>(i));
+        row2.values.emplace_back(folly::to<std::string>(i % 11));
+        row2.values.emplace_back(folly::to<std::string>(i % 11));
+        row2.values.emplace_back(i % 11 * 2 + 1);
+        row2.values.emplace_back(i % 11 * 2 + 2);
+        row2.values.emplace_back(folly::to<std::string>(i - 5));
+        expected.rows.emplace_back(std::move(row2));
+    }
+
+    // $var1 inner join $var2 on $var2.dst = $var1._vid
+    testInnerJoin("var2", "var1", expected, __LINE__);
+}
+
+TEST_F(JoinTest, InnerJoinTwice) {
+    std::string joinOutput;
+    {
+        std::string left = "var2";
+        std::string right = "var1";
+        VariablePropertyExpression key(new std::string(left),
+                                    new std::string("dst"));
+        std::vector<Expression*> hashKeys = {&key};
+        VariablePropertyExpression probe(new std::string(right),
+                                        new std::string("_vid"));
+        std::vector<Expression*> probeKeys = {&probe};
+
+        auto* join =
+            InnerJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
+                        std::move(probeKeys));
+        join->setColNames(
+            std::vector<std::string>{"src", "dst", kVid, "tag_prop", "edge_prop", kDst});
+
+        auto joinExe =
+            std::make_unique<InnerJoinExecutor>(join, qctx_.get());
+        auto future = joinExe->execute();
+
+        joinOutput = join->outputVar();
+    }
+
+    std::string left = joinOutput;
+    std::string right = "var3";
+    VariablePropertyExpression key(new std::string(left),
+                                new std::string("src"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right),
+                                    new std::string("col1"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join =
+        InnerJoin::make(qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys),
+                    std::move(probeKeys));
+    join->setColNames(std::vector<std::string>{
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"});
+
+    auto joinExe =
+        std::make_unique<InnerJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok());
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    DataSet expected;
+    expected.colNames = {
+        "src", "dst", kVid, "tag_prop", "edge_prop", kDst, "col1"};
+    Row row1;
+    row1.values.emplace_back("11");
+    row1.values.emplace_back("0");
+    row1.values.emplace_back("0");
+    row1.values.emplace_back(0);
+    row1.values.emplace_back(1);
+    row1.values.emplace_back("5");
+    row1.values.emplace_back("11");
+    expected.rows.emplace_back(row1);
+    row1.values[3] = 1;
+    row1.values[4] = 2;
+    row1.values[5] = "6";
+    expected.rows.emplace_back(std::move(row1));
+
+    EXPECT_EQ(resultDs, expected);
+    EXPECT_EQ(result.state(), Result::State::kSuccess);
+}
+
+TEST_F(JoinTest, InnerJoinEmpty) {
+    {
+        DataSet expected;
+        expected.colNames = {
+            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
+
+        testInnerJoin("var2", "empty_var1", expected, __LINE__);
+    }
+
+    {
+        DataSet expected;
+        expected.colNames = {
+            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
+
+        testInnerJoin("empty_var2", "var1", expected, __LINE__);
+    }
+
+    {
+        DataSet expected;
+        expected.colNames = {
+            "src", "dst", kVid, "tag_prop", "edge_prop", kDst};
+
+        testInnerJoin("empty_var2", "empty_var1", expected, __LINE__);
+    }
+}
+
+TEST_F(JoinTest, LeftJoin) {
+    DataSet expected;
+    expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst"};
+    for (auto i = 0; i < 10; ++i) {
+        Row row;
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        row.values.emplace_back(i);
+        row.values.emplace_back(i + 1);
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 11));
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        expected.rows.emplace_back(std::move(row));
+    }
+
+    // $var1 left join $var2 on $var1._vid = $var2.dst
+    testLeftJoin("var1", "var2", expected, __LINE__);
+}
+
+TEST_F(JoinTest, LeftJoinTwice) {
+    std::string joinOutput;
+    {
+        std::string left = "var1";
+        std::string right = "var2";
+        VariablePropertyExpression key(new std::string(left), new std::string("_vid"));
+        std::vector<Expression*> hashKeys = {&key};
+        VariablePropertyExpression probe(new std::string(right), new std::string("dst"));
+        std::vector<Expression*> probeKeys = {&probe};
+
+        auto* join = LeftJoin::make(
+            qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+        join->setColNames(
+            std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst"});
+
+        auto joinExe = std::make_unique<LeftJoinExecutor>(join, qctx_.get());
+        auto future = joinExe->execute();
+
+        joinOutput = join->outputVar();
+    }
+
+    std::string left = joinOutput;
+    std::string right = "var3";
+    VariablePropertyExpression key(new std::string(left), new std::string("src"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right), new std::string("col1"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join = LeftJoin::make(
+        qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+    join->setColNames(
+        std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"});
+
+    auto joinExe = std::make_unique<LeftJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok());
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    DataSet expected;
+    expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    for (auto i = 0; i < 10; ++i) {
+        Row row;
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        row.values.emplace_back(i);
+        row.values.emplace_back(i + 1);
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 11));
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        if (i < 2) {
+            row.values.emplace_back(folly::to<std::string>(11));
+        } else {
+            row.values.emplace_back(Value::kEmpty);
+        }
+        expected.rows.emplace_back(std::move(row));
+    }
+    EXPECT_EQ(resultDs, expected);
+    EXPECT_EQ(result.state(), Result::State::kSuccess);
+}
+
+TEST_F(JoinTest, LeftJoinEmpty) {
+    {
+        DataSet expected;
+        expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst"};
+        for (auto i = 0; i < 10; ++i) {
+            Row row;
+            row.values.emplace_back(folly::to<std::string>(i / 2));
+            row.values.emplace_back(i);
+            row.values.emplace_back(i + 1);
+            row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+            row.values.emplace_back(Value::kEmpty);
+            row.values.emplace_back(Value::kEmpty);
+            expected.rows.emplace_back(std::move(row));
+        }
+        testLeftJoin("var1", "empty_var2", expected, __LINE__);
+    }
+
+    {
+        DataSet expected;
+        expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst"};
+        testLeftJoin("empty_var1", "var2", expected, __LINE__);
+    }
+
+    {
+        DataSet expected;
+        expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst"};
+        testLeftJoin("empty_var1", "empty_var2", expected, __LINE__);
+    }
+}
+
+TEST_F(JoinTest, LeftJoinAndInnerjoin) {
+    std::string joinOutput;
+    {
+        std::string left = "var1";
+        std::string right = "var2";
+        VariablePropertyExpression key(new std::string(left), new std::string("_vid"));
+        std::vector<Expression*> hashKeys = {&key};
+        VariablePropertyExpression probe(new std::string(right), new std::string("dst"));
+        std::vector<Expression*> probeKeys = {&probe};
+
+        auto* join = LeftJoin::make(
+            qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+        join->setColNames(
+            std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst"});
+
+        auto joinExe = std::make_unique<LeftJoinExecutor>(join, qctx_.get());
+        auto future = joinExe->execute();
+
+        joinOutput = join->outputVar();
+    }
+
+    std::string left = joinOutput;
+    std::string right = "var3";
+    VariablePropertyExpression key(new std::string(left), new std::string("src"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right), new std::string("col1"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join = InnerJoin::make(
+        qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+    join->setColNames(
+        std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"});
+
+    auto joinExe = std::make_unique<InnerJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok());
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    DataSet expected;
+    expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    for (auto i = 0; i < 2; ++i) {
+        Row row;
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        row.values.emplace_back(i);
+        row.values.emplace_back(i + 1);
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 11));
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        row.values.emplace_back(folly::to<std::string>(11));
+        expected.rows.emplace_back(std::move(row));
+    }
+    EXPECT_EQ(resultDs, expected);
+    EXPECT_EQ(result.state(), Result::State::kSuccess);
+}
+
+TEST_F(JoinTest, InnerJoinAndLeftjoin) {
+    std::string joinOutput;
+    {
+        std::string left = "var1";
+        std::string right = "var2";
+        VariablePropertyExpression key(new std::string(left), new std::string("_vid"));
+        std::vector<Expression*> hashKeys = {&key};
+        VariablePropertyExpression probe(new std::string(right), new std::string("dst"));
+        std::vector<Expression*> probeKeys = {&probe};
+
+        auto* join = InnerJoin::make(
+            qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+        join->setColNames(
+            std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst"});
+
+        auto joinExe = std::make_unique<InnerJoinExecutor>(join, qctx_.get());
+        auto future = joinExe->execute();
+
+        joinOutput = join->outputVar();
+    }
+
+    std::string left = joinOutput;
+    std::string right = "var3";
+    VariablePropertyExpression key(new std::string(left), new std::string("src"));
+    std::vector<Expression*> hashKeys = {&key};
+    VariablePropertyExpression probe(new std::string(right), new std::string("col1"));
+    std::vector<Expression*> probeKeys = {&probe};
+
+    auto* join = LeftJoin::make(
+        qctx_.get(), nullptr, {left, 0}, {right, 0}, std::move(hashKeys), std::move(probeKeys));
+    join->setColNames(
+        std::vector<std::string>{kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"});
+
+    auto joinExe = std::make_unique<LeftJoinExecutor>(join, qctx_.get());
+    auto future = joinExe->execute();
+    auto status = std::move(future).get();
+    EXPECT_TRUE(status.ok());
+    auto& result = qctx_->ectx()->getResult(join->outputVar());
+
+    DataSet resultDs;
+    resultDs.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    auto iter = result.iter();
+    for (; iter->valid(); iter->next()) {
+        const auto& cols = *iter->row();
+        Row row;
+        for (size_t i = 0; i < cols.size(); ++i) {
+            Value col = cols[i];
+            row.values.emplace_back(std::move(col));
+        }
+        resultDs.rows.emplace_back(std::move(row));
+    }
+
+    DataSet expected;
+    expected.colNames = {kVid, "tag_prop", "edge_prop", kDst, "src", "dst", "col1"};
+    for (auto i = 0; i < 10; ++i) {
+        Row row;
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        row.values.emplace_back(i);
+        row.values.emplace_back(i + 1);
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 5 + i % 2));
+        row.values.emplace_back(folly::to<std::string>(i / 2 + 11));
+        row.values.emplace_back(folly::to<std::string>(i / 2));
+        if (i < 2) {
+            row.values.emplace_back(folly::to<std::string>(11));
+        } else {
+            row.values.emplace_back(Value::kEmpty);
+        }
+        expected.rows.emplace_back(std::move(row));
+    }
+    EXPECT_EQ(resultDs, expected);
+    EXPECT_EQ(result.state(), Result::State::kSuccess);
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp
index edd18cb7..4d8b142b 100644
--- a/src/planner/PlanNode.cpp
+++ b/src/planner/PlanNode.cpp
@@ -179,8 +179,10 @@ const char* PlanNode::toString(PlanNode::Kind kind) {
             return "ShowBalance";
         case Kind::kSubmitJob:
             return "SubmitJob";
-        case Kind::kDataJoin:
-            return "DataJoin";
+        case Kind::kLeftJoin:
+            return "LeftJoin";
+        case Kind::kInnerJoin:
+            return "InnerJoin";
         case Kind::kDeleteVertices:
             return "DeleteVertices";
         case Kind::kDeleteEdges:
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 4ca23852..14977c19 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -100,7 +100,8 @@ public:
         kCreateSnapshot,
         kDropSnapshot,
         kShowSnapshots,
-        kDataJoin,
+        kLeftJoin,
+        kInnerJoin,
         kDeleteVertices,
         kDeleteEdges,
         kUpdateVertex,
diff --git a/src/planner/Query.cpp b/src/planner/Query.cpp
index 3302bb97..4e80790d 100644
--- a/src/planner/Query.cpp
+++ b/src/planner/Query.cpp
@@ -271,7 +271,7 @@ std::unique_ptr<PlanNodeDescription> DataCollect::explain() const {
     return desc;
 }
 
-std::unique_ptr<PlanNodeDescription> DataJoin::explain() const {
+std::unique_ptr<PlanNodeDescription> LeftJoin::explain() const {
     auto desc = SingleDependencyNode::explain();
     folly::dynamic inputVar = folly::dynamic::object();
     inputVar.insert("leftVar", util::toJson(leftVar_));
@@ -279,6 +279,19 @@ std::unique_ptr<PlanNodeDescription> DataJoin::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());
+    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());
+    addDescription("kind", "InnerJoin", desc.get());
     return desc;
 }
 
diff --git a/src/planner/Query.h b/src/planner/Query.h
index 24876819..8d9f8b99 100644
--- a/src/planner/Query.h
+++ b/src/planner/Query.h
@@ -962,23 +962,31 @@ private:
     bool                        distinct_{false};
 };
 
-/**
- * An implementation of inner join which join two given variable.
- */
-class DataJoin final : public SingleDependencyNode {
+class Join : public SingleDependencyNode {
 public:
-    static DataJoin* make(QueryContext* qctx,
-                          PlanNode* input,
-                          std::pair<std::string, int64_t> leftVar,
-                          std::pair<std::string, int64_t> rightVar,
-                          std::vector<Expression*> hashKeys,
-                          std::vector<Expression*> probeKeys) {
-        return qctx->objPool()->add(new DataJoin(qctx,
-                                                 input,
-                                                 std::move(leftVar),
-                                                 std::move(rightVar),
-                                                 std::move(hashKeys),
-                                                 std::move(probeKeys)));
+    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 {
@@ -997,39 +1005,66 @@ public:
         return probeKeys_;
     }
 
+protected:
+    // var name, var version
+    std::pair<std::string, int64_t>         leftVar_;
+    std::pair<std::string, int64_t>         rightVar_;
+    std::vector<Expression*>                hashKeys_;
+    std::vector<Expression*>                probeKeys_;
+};
+
+/*
+ *  left join
+ */
+class LeftJoin final : public Join {
+public:
+    static LeftJoin* make(QueryContext* qctx,
+                          PlanNode* input,
+                          std::pair<std::string, int64_t> leftVar,
+                          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));
+    }
+
     std::unique_ptr<PlanNodeDescription> explain() const override;
 
 private:
-    DataJoin(QueryContext* qctx,
+    LeftJoin(QueryContext* qctx,
              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::kDataJoin, 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);
+        : Join(qctx, Kind::kLeftJoin, input, leftVar, rightVar, hashKeys, probeKeys) {}
+};
 
-        auto* rightVarPtr = qctx_->symTable()->getVar(rightVar_.first);
-        DCHECK(rightVarPtr != nullptr);
-        inputVars_.emplace_back(rightVarPtr);
-        qctx_->symTable()->readBy(rightVarPtr->name, this);
+/*
+ *  inner join
+ */
+class InnerJoin final : public Join {
+public:
+    static InnerJoin* make(QueryContext* qctx,
+                           PlanNode* input,
+                           std::pair<std::string, int64_t> leftVar,
+                           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));
     }
 
+    std::unique_ptr<PlanNodeDescription> explain() const override;
+
 private:
-    // var name, var version
-    std::pair<std::string, int64_t>         leftVar_;
-    std::pair<std::string, int64_t>         rightVar_;
-    std::vector<Expression*>                hashKeys_;
-    std::vector<Expression*>                probeKeys_;
+    InnerJoin(QueryContext* qctx,
+              PlanNode* input,
+              std::pair<std::string, int64_t> leftVar,
+              std::pair<std::string, int64_t> rightVar,
+              std::vector<Expression*> hashKeys,
+              std::vector<Expression*> probeKeys)
+        : Join(qctx, Kind::kInnerJoin, input, leftVar, rightVar, hashKeys, probeKeys) {}
 };
 
 /*
diff --git a/src/planner/match/InnerJoinStrategy.cpp b/src/planner/match/InnerJoinStrategy.cpp
index 9815301b..38e87c04 100644
--- a/src/planner/match/InnerJoinStrategy.cpp
+++ b/src/planner/match/InnerJoinStrategy.cpp
@@ -38,7 +38,7 @@ PlanNode* InnerJoinStrategy::joinDataSet(const PlanNode* left, const PlanNode* r
 
     qctx_->objPool()->add(buildExpr);
     qctx_->objPool()->add(probeExpr);
-    auto join = DataJoin::make(qctx_,
+    auto join = InnerJoin::make(qctx_,
                                const_cast<PlanNode*>(right),
                                {left->outputVar(), 0},
                                {right->outputVar(), 0},
diff --git a/src/validator/GoValidator.cpp b/src/validator/GoValidator.cpp
index a49992e6..9dfc552b 100644
--- a/src/validator/GoValidator.cpp
+++ b/src/validator/GoValidator.cpp
@@ -473,7 +473,7 @@ PlanNode* GoValidator::buildJoinDstProps(PlanNode* projectSrcDstProps) {
         new std::string(joinDstVidColName_));
     auto* probeKey = objPool->makeAndAdd<VariablePropertyExpression>(
         new std::string(project->outputVar()), new std::string(vidColName));
-    auto joinDst = DataJoin::make(qctx_, project,
+    auto joinDst = InnerJoin::make(qctx_, project,
             {projectSrcDstProps->outputVar(), ExecutionContext::kLatestVersion},
             {project->outputVar(), ExecutionContext::kLatestVersion},
             {joinHashKey}, {probeKey});
@@ -499,7 +499,7 @@ PlanNode* GoValidator::buildJoinPipeOrVariableInput(PlanNode* projectFromJoin,
         auto* probeKey = pool->add(new VariablePropertyExpression(
             new std::string(projectFromJoin->outputVar()), new std::string(dstVidColName_)));
         auto* join =
-            DataJoin::make(qctx_,
+            InnerJoin::make(qctx_,
                            dependencyForJoinInput,
                            {dependencyForJoinInput->outputVar(), ExecutionContext::kLatestVersion},
                            {projectFromJoin->outputVar(),
@@ -524,7 +524,7 @@ PlanNode* GoValidator::buildJoinPipeOrVariableInput(PlanNode* projectFromJoin,
                                                            : kVid)));
     std::string varName = from_.fromType == kPipe ? inputVarName_ : from_.userDefinedVarName;
     auto* joinInput =
-        DataJoin::make(qctx_, dependencyForJoinInput,
+        InnerJoin::make(qctx_, dependencyForJoinInput,
                         {dependencyForJoinInput->outputVar(),
                         ExecutionContext::kLatestVersion},
                         {varName,
@@ -555,7 +555,7 @@ PlanNode* GoValidator::traceToStartVid(PlanNode* projectLeftVarForJoin,
     auto probeKey = new VariablePropertyExpression(
         new std::string(dedupDstVids->outputVar()), new std::string(srcVidColName_));
     pool->add(probeKey);
-    auto* join = DataJoin::make(
+    auto* join = InnerJoin::make(
         qctx_, dedupDstVids,
         {projectLeftVarForJoin->outputVar(),
             ExecutionContext::kLatestVersion},
diff --git a/src/validator/test/GroupByValidatorTest.cpp b/src/validator/test/GroupByValidatorTest.cpp
index 501051d0..5eb16a49 100644
--- a/src/validator/test/GroupByValidatorTest.cpp
+++ b/src/validator/test/GroupByValidatorTest.cpp
@@ -73,7 +73,7 @@ TEST_F(GroupByValidatorTest, TestGroupBy) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -103,7 +103,7 @@ TEST_F(GroupByValidatorTest, TestGroupBy) {
             PK::kProject,
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -128,7 +128,7 @@ TEST_F(GroupByValidatorTest, TestGroupBy) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -154,7 +154,7 @@ TEST_F(GroupByValidatorTest, TestGroupBy) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -180,7 +180,7 @@ TEST_F(GroupByValidatorTest, TestGroupBy) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -217,7 +217,7 @@ TEST_F(GroupByValidatorTest, VariableTest) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -243,7 +243,7 @@ TEST_F(GroupByValidatorTest, VariableTest) {
         std::vector<PlanNode::Kind> expected = {
             PK::kAggregate,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
diff --git a/src/validator/test/MatchValidatorTest.cpp b/src/validator/test/MatchValidatorTest.cpp
index 431a6980..4e0c0887 100644
--- a/src/validator/test/MatchValidatorTest.cpp
+++ b/src/validator/test/MatchValidatorTest.cpp
@@ -52,7 +52,7 @@ TEST_F(MatchValidatorTest, SeekByTagIndex) {
         std::vector<PlanNode::Kind> expected = {PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kGetVertices,
@@ -183,7 +183,7 @@ TEST_F(MatchValidatorTest, groupby) {
         std::vector<PlanNode::Kind> expected = {PlanNode::Kind::kAggregate,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -211,7 +211,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kAggregate,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -240,7 +240,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -272,7 +272,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -303,7 +303,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -336,7 +336,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -372,7 +372,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -407,7 +407,7 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
@@ -443,12 +443,12 @@ TEST_F(MatchValidatorTest, groupby) {
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetNeighbors,
@@ -476,7 +476,7 @@ TEST_F(MatchValidatorTest, with) {
                                                 PlanNode::Kind::kAggregate,
                                                 PlanNode::Kind::kFilter,
                                                 PlanNode::Kind::kProject,
-                                                PlanNode::Kind::kDataJoin,
+                                                PlanNode::Kind::kInnerJoin,
                                                 PlanNode::Kind::kProject,
                                                 PlanNode::Kind::kGetVertices,
                                                 PlanNode::Kind::kDedup,
diff --git a/src/validator/test/QueryValidatorTest.cpp b/src/validator/test/QueryValidatorTest.cpp
index 8fee40e0..1e93d49b 100644
--- a/src/validator/test/QueryValidatorTest.cpp
+++ b/src/validator/test/QueryValidatorTest.cpp
@@ -60,7 +60,7 @@ TEST_F(QueryValidatorTest, GoZeroStep) {
                             "| GO FROM $-.id OVER serve";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -155,8 +155,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "id | GO 2 STEPS FROM $-.id OVER like";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kLoop,
@@ -165,7 +165,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
@@ -184,7 +184,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "| GO 1 STEPS FROM $-.id OVER like";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -204,7 +204,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
         std::string query = "YIELD \"1\" AS id | GO FROM $-.id OVER like";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -219,7 +219,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "id | GO 1 STEPS FROM $-.id OVER like YIELD $-.id, like._dst";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -237,7 +237,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -257,7 +257,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDedup,
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -273,8 +273,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "id | GO 2 STEPS FROM $-.id OVER like YIELD $-.id, like._dst";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kLoop,
@@ -283,7 +283,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
@@ -304,8 +304,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kLoop,
@@ -314,7 +314,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
@@ -337,8 +337,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDedup,
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kLoop,
@@ -347,7 +347,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
@@ -368,8 +368,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "$$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -379,7 +379,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -399,8 +399,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDataCollect,
             PK::kDedup,
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -410,7 +410,7 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -428,8 +428,8 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "YIELD $-.name, $^.person.name, $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -451,9 +451,9 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
                             "$$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -466,12 +466,12 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kDedup,
             PK::kProject,
             PK::kProject,
@@ -495,9 +495,9 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kDataCollect,
             PK::kDedup,
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -510,12 +510,12 @@ TEST_F(QueryValidatorTest, GoWithPipe) {
             PK::kProject,
             PK::kProject,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kDedup,
             PK::kProject,
             PK::kProject,
@@ -552,8 +552,8 @@ TEST_F(QueryValidatorTest, GoWithVariable) {
                             "YIELD $var.name, $^.person.name, $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
-            PK::kDataJoin,
+            PK::kInnerJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -576,7 +576,7 @@ TEST_F(QueryValidatorTest, GoReversely) {
                             "YIELD $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -592,7 +592,7 @@ TEST_F(QueryValidatorTest, GoReversely) {
                             "YIELD $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -625,7 +625,7 @@ TEST_F(QueryValidatorTest, GoBidirectly) {
                             "YIELD $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -690,7 +690,7 @@ TEST_F(QueryValidatorTest, GoOneStep) {
                             "YIELD $$.person.name,$$.person.age";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -707,7 +707,7 @@ TEST_F(QueryValidatorTest, GoOneStep) {
                             "$$.person.name, $$.person.age + 1";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -726,7 +726,7 @@ TEST_F(QueryValidatorTest, GoOneStep) {
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -747,7 +747,7 @@ TEST_F(QueryValidatorTest, GoOneStep) {
             PK::kDedup,
             PK::kProject,
             PK::kFilter,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -805,7 +805,7 @@ TEST_F(QueryValidatorTest, GoOneStep) {
                             "| GO FROM $-.id OVER like";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -851,7 +851,7 @@ TEST_F(QueryValidatorTest, GoOverAll) {
         std::string query  = "GO FROM \"1\" OVER * YIELD $$.person.name";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -871,13 +871,13 @@ TEST_F(QueryValidatorTest, OutputToAPipe) {
             "| ( GO FROM $-.id OVER like YIELD like._dst as id | GO FROM $-.id OVER serve )";
         std::vector<PlanNode::Kind> expected = {
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetNeighbors,
             PK::kDedup,
@@ -933,7 +933,7 @@ TEST_F(QueryValidatorTest, GoMToN) {
             PK::kStart,
             PK::kDedup,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -1002,7 +1002,7 @@ TEST_F(QueryValidatorTest, GoMToN) {
             PK::kLoop,
             PK::kStart,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -1025,9 +1025,9 @@ TEST_F(QueryValidatorTest, GoMToN) {
             PK::kDedup,
             PK::kProject,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kDedup,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kProject,
             PK::kProject,
@@ -1035,7 +1035,7 @@ TEST_F(QueryValidatorTest, GoMToN) {
             PK::kGetNeighbors,
             PK::kProject,
             PK::kStart,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kDedup,
             PK::kProject,
             PK::kDedup,
@@ -1287,7 +1287,7 @@ TEST_F(QueryValidatorTest, TestMatch) {
             PK::kProject,
             PK::kFilter,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -1312,12 +1312,12 @@ TEST_F(QueryValidatorTest, TestMatch) {
             PK::kProject,
             PK::kFilter,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kFilter,
             PK::kProject,
             PK::kGetNeighbors,
@@ -1343,7 +1343,7 @@ TEST_F(QueryValidatorTest, TestMatch) {
             PK::kFilter,
             PK::kFilter,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
@@ -1366,7 +1366,7 @@ TEST_F(QueryValidatorTest, TestMatch) {
                                                 PK::kFilter,
                                                 PK::kFilter,
                                                 PK::kProject,
-                                                PK::kDataJoin,
+                                                PK::kInnerJoin,
                                                 PK::kProject,
                                                 PK::kGetVertices,
                                                 PK::kDedup,
@@ -1379,7 +1379,7 @@ TEST_F(QueryValidatorTest, TestMatch) {
                                                 PK::kFilter,
                                                 PK::kProject,
                                                 PK::kGetNeighbors,
-                                                PK::kDataJoin,
+                                                PK::kInnerJoin,
                                                 PK::kDedup,
                                                 PK::kProject,
                                                 PK::kProject,
@@ -1398,7 +1398,7 @@ TEST_F(QueryValidatorTest, TestMatch) {
             PK::kProject,
             PK::kFilter,
             PK::kProject,
-            PK::kDataJoin,
+            PK::kInnerJoin,
             PK::kProject,
             PK::kGetVertices,
             PK::kDedup,
diff --git a/src/validator/test/SymbolsTest.cpp b/src/validator/test/SymbolsTest.cpp
index 02c73b90..efa1ed96 100644
--- a/src/validator/test/SymbolsTest.cpp
+++ b/src/validator/test/SymbolsTest.cpp
@@ -172,7 +172,7 @@ TEST_F(SymbolsTest, Variables) {
             EXPECT_TRUE(checkNodes(variable->writtenBy, {12}));
         }
         {
-            auto varName = "__DataJoin_13";
+            auto varName = "__InnerJoin_13";
             auto* variable = symTable->getVar(varName);
             EXPECT_NE(variable, nullptr);
             EXPECT_EQ(variable->name, varName);
@@ -223,7 +223,7 @@ TEST_F(SymbolsTest, Variables) {
             EXPECT_TRUE(checkNodes(variable->writtenBy, {18}));
         }
         {
-            auto varName = "__DataJoin_19";
+            auto varName = "__InnerJoin_19";
             auto* variable = symTable->getVar(varName);
             EXPECT_NE(variable, nullptr);
             EXPECT_EQ(variable->name, varName);
@@ -234,7 +234,7 @@ TEST_F(SymbolsTest, Variables) {
             EXPECT_TRUE(checkNodes(variable->writtenBy, {19}));
         }
         {
-            auto varName = "__DataJoin_20";
+            auto varName = "__InnerJoin_20";
             auto* variable = symTable->getVar(varName);
             EXPECT_NE(variable, nullptr);
             EXPECT_EQ(variable->name, varName);
diff --git a/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature b/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature
index 5f20cb22..edd4c48d 100644
--- a/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature
+++ b/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature
@@ -23,7 +23,7 @@ Feature: merge get neighbors, dedup and project rule
       | Project            | 1            |               |
       | Filter             | 2            |               |
       | Project            | 3            |               |
-      | DataJoin           | 4            |               |
+      | InnerJoin          | 4            |               |
       | Project            | 5            |               |
       | GetVertices        | 6            | dedup: true   |
       | Filter             | 7            |               |
@@ -31,7 +31,7 @@ Feature: merge get neighbors, dedup and project rule
       | Loop               | 15           | loopBody: 9   |
       | Filter             | 10           |               |
       | Project            | 11           |               |
-      | DataJoin           | 12           |               |
+      | InnerJoin          | 12           |               |
       | Project            | 13           |               |
       | GetNeighbors       | 14           | dedup: true   |
       | Start              |              |               |
-- 
GitLab