From 3207041dedb3a8e8be91f455538472dea69ce525 Mon Sep 17 00:00:00 2001
From: bright-starry-sky <56461666+bright-starry-sky@users.noreply.github.com>
Date: Sun, 27 Sep 2020 10:35:20 +0800
Subject: [PATCH] Optimizer rule for index scan (#272)

* optimizer_rule_index

* added test cases

* Addressed yee's comments

* Addressed CPWstatic's comments

* To improve the FilterItems from tuple to struct

* fixed column def error;  improved error message.

* Improve code

* Resolved conflict

* Removed useless code

* LabelAttributeExpression rewrite
---
 src/optimizer/CMakeLists.txt             |   1 +
 src/optimizer/rule/IndexScanRule.cpp     | 554 +++++++++++++++++++++++
 src/optimizer/rule/IndexScanRule.h       | 174 +++++++
 src/optimizer/test/CMakeLists.txt        |  93 ++--
 src/optimizer/test/IndexScanRuleTest.cpp | 227 ++++++++++
 src/planner/Query.h                      |   2 +-
 src/validator/IndexScanValidator.cpp     | 163 +++++--
 src/validator/IndexScanValidator.h       |   6 +
 src/validator/Validator.cpp              |   4 +-
 9 files changed, 1138 insertions(+), 86 deletions(-)
 create mode 100644 src/optimizer/rule/IndexScanRule.cpp
 create mode 100644 src/optimizer/rule/IndexScanRule.h
 create mode 100644 src/optimizer/test/IndexScanRuleTest.cpp

diff --git a/src/optimizer/CMakeLists.txt b/src/optimizer/CMakeLists.txt
index 39cbaea8..248d05f0 100644
--- a/src/optimizer/CMakeLists.txt
+++ b/src/optimizer/CMakeLists.txt
@@ -11,6 +11,7 @@ nebula_add_library(
     OptGroup.cpp
     OptRule.cpp
     rule/PushFilterDownGetNbrsRule.cpp
+    rule/IndexScanRule.cpp
 )
 
 nebula_add_subdirectory(test)
diff --git a/src/optimizer/rule/IndexScanRule.cpp b/src/optimizer/rule/IndexScanRule.cpp
new file mode 100644
index 00000000..dbed5107
--- /dev/null
+++ b/src/optimizer/rule/IndexScanRule.cpp
@@ -0,0 +1,554 @@
+/* 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 "optimizer/rule/IndexScanRule.h"
+#include "common/expression/LabelAttributeExpression.h"
+#include "optimizer/OptGroup.h"
+#include "planner/PlanNode.h"
+#include "planner/Query.h"
+
+namespace nebula {
+namespace opt {
+std::unique_ptr<OptRule> IndexScanRule::kInstance =
+    std::unique_ptr<IndexScanRule>(new IndexScanRule());
+
+IndexScanRule::IndexScanRule() {
+    RuleSet::defaultRules().addRule(this);
+}
+
+bool IndexScanRule::match(const OptGroupExpr *groupExpr) const {
+    return groupExpr->node()->kind() == PlanNode::Kind::kIndexScan;
+}
+
+Status IndexScanRule::transform(graph::QueryContext *qctx,
+                                const OptGroupExpr *groupExpr,
+                                TransformResult *result) const {
+    FilterItems items;
+    ScanKind kind;
+    auto filter = filterExpr(groupExpr);
+    if (filter == nullptr) {
+        return Status::SemanticError("WHERE clause error");
+    }
+    auto ret = analyzeExpression(filter.get(), &items, &kind, isEdge(groupExpr));
+    NG_RETURN_IF_ERROR(ret);
+
+    IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>();
+    ret = createIndexQueryCtx(iqctx, kind, items, qctx, groupExpr);
+    NG_RETURN_IF_ERROR(ret);
+
+    auto newIN = cloneIndexScan(qctx, groupExpr);
+    newIN->setIndexQueryContext(std::move(iqctx));
+    auto newGroupExpr = OptGroupExpr::create(qctx, newIN, groupExpr->group());
+    if (groupExpr->dependencies().size() != 1) {
+        return Status::Error("Plan node dependencies error");
+    }
+    newGroupExpr->dependsOn(groupExpr->dependencies()[0]);
+    result->newGroupExprs.emplace_back(newGroupExpr);
+    result->eraseAll = true;
+    return Status::OK();
+}
+
+std::string IndexScanRule::toString() const {
+    return "IndexScanRule";
+}
+
+Status IndexScanRule::createIndexQueryCtx(IndexQueryCtx &iqctx,
+                                          ScanKind kind,
+                                          const FilterItems& items,
+                                          graph::QueryContext *qctx,
+                                          const OptGroupExpr *groupExpr) const {
+    return kind.isLogicalAnd()
+           ? createIQCWithLogicAnd(iqctx, items, qctx, groupExpr)
+           : createIQCWithLogicOR(iqctx, items, qctx, groupExpr);
+}
+
+Status IndexScanRule::createIQCWithLogicAnd(IndexQueryCtx &iqctx,
+                                            const FilterItems& items,
+                                            graph::QueryContext *qctx,
+                                            const OptGroupExpr *groupExpr) const {
+    auto index = findOptimalIndex(qctx, groupExpr, items);
+    if (index == nullptr) {
+        return Status::IndexNotFound("No valid index found");
+    }
+
+    return appendIQCtx(index, items, iqctx);
+}
+
+Status IndexScanRule::createIQCWithLogicOR(IndexQueryCtx &iqctx,
+                                           const FilterItems& items,
+                                           graph::QueryContext *qctx,
+                                           const OptGroupExpr *groupExpr) const {
+    for (auto const& item : items.items) {
+        auto index = findOptimalIndex(qctx, groupExpr, FilterItems({item}));
+        if (index == nullptr) {
+            return Status::IndexNotFound("No valid index found");
+        }
+        auto ret = appendIQCtx(index, FilterItems({item}), iqctx);
+        NG_RETURN_IF_ERROR(ret);
+    }
+    return Status::OK();
+}
+
+Status IndexScanRule::appendIQCtx(const IndexItem& index,
+                                  const FilterItems& items,
+                                  IndexQueryCtx &iqctx) const {
+    auto fields = index->get_fields();
+    IndexQueryContext ctx;
+    decltype(ctx.column_hints) hints;
+    for (const auto& field : fields) {
+        bool found = false;
+        FilterItems filterItems;
+        for (const auto& item : items.items) {
+            if (item.col_ != field.get_name()) {
+                continue;
+            }
+            filterItems.addItem(item.col_, item.relOP_, item.value_);
+            found = true;
+        }
+        if (!found) break;
+        // TODO (sky) : rewrite filter expr. NE expr should be add filter expr .
+        auto it = std::find_if(filterItems.items.begin(), filterItems.items.end(),
+                               [](const auto &ite) {
+                                   return ite.relOP_ == RelationalExpression::Kind::kRelNE;
+                               });
+        if (it != filterItems.items.end()) {
+            break;
+        }
+        auto ret = appendColHint(hints, filterItems, field);
+        NG_RETURN_IF_ERROR(ret);
+    }
+    ctx.set_index_id(index->get_index_id());
+    // TODO (sky) : rewrite expr and set filter
+    ctx.set_column_hints(std::move(hints));
+    iqctx->emplace_back(std::move(ctx));
+    return Status::OK();
+}
+
+#define CHECK_BOUND_VALUE(v, name)                                                                 \
+    do {                                                                                           \
+        if (v == Value(NullType::BAD_TYPE)) {                                                      \
+            LOG(ERROR) << "Get bound value error. field : "  << name;                              \
+            return Status::Error("Get bound value error. field : %s", name.c_str());               \
+        }                                                                                          \
+    } while (0)
+
+Status IndexScanRule::appendColHint(std::vector<IndexColumnHint>& hints,
+                                    const FilterItems& items,
+                                    const meta::cpp2::ColumnDef& col) const {
+    IndexColumnHint hint;
+    Value begin, end;
+    bool isRangeScan = true;
+    for (const auto& item : items.items) {
+        if (item.relOP_ == Expression::Kind::kRelEQ) {
+            // check the items, don't allow where c1 == 1 and c1 == 2 and c1 > 3....
+            // If EQ item appears, only one element is allowed
+            if (items.items.size() > 1) {
+                return Status::SemanticError();
+            }
+            isRangeScan = false;
+            begin = item.value_;
+            break;
+        }
+        auto ret = boundValue(item, col, begin, end);
+        NG_RETURN_IF_ERROR(ret);
+    }
+
+    if (isRangeScan) {
+        if (begin == Value()) {
+            begin = OptimizerUtils::boundValue(col, BVO::MIN, Value());
+            CHECK_BOUND_VALUE(begin, col.get_name());
+        }
+        if (end == Value()) {
+            end = OptimizerUtils::boundValue(col, BVO::MAX, Value());
+            CHECK_BOUND_VALUE(end, col.get_name());
+        }
+        hint.set_scan_type(storage::cpp2::ScanType::RANGE);
+        hint.set_end_value(std::move(end));
+    } else {
+        hint.set_scan_type(storage::cpp2::ScanType::PREFIX);
+    }
+    hint.set_begin_value(std::move(begin));
+    hint.set_column_name(col.get_name());
+    hints.emplace_back(std::move(hint));
+    return Status::OK();
+}
+
+Status IndexScanRule::boundValue(const FilterItem& item,
+                                 const meta::cpp2::ColumnDef& col,
+                                 Value& begin, Value& end) const {
+    auto val = item.value_;
+    if (val.type() != graph::SchemaUtil::propTypeToValueType(col.type.type)) {
+        return Status::SemanticError("Data type error of field : %s", col.get_name().c_str());
+    }
+    switch (item.relOP_) {
+        case Expression::Kind::kRelLE: {
+            // if c1 <= int(5) , the range pair should be (min, 6)
+            // if c1 < int(5), the range pair should be (min, 5)
+            auto v = OptimizerUtils::boundValue(col, BVO::GREATER_THAN, val);
+            CHECK_BOUND_VALUE(v, col.get_name());
+            // where c <= 1 and c <= 2 , 1 should be valid.
+            if (end == Value()) {
+                end = v;
+            } else {
+                end = v < end ? v : end;
+            }
+            break;
+        }
+        case Expression::Kind::kRelGE: {
+            // where c >= 1 and c >= 2 , 2 should be valid.
+            if (begin == Value()) {
+                begin = val;
+            } else {
+                begin = val < begin ? begin : val;
+            }
+            break;
+        }
+        case Expression::Kind::kRelLT: {
+            // c < 5 and c < 6 , 5 should be valid.
+            if (end == Value()) {
+                end = val;
+            } else {
+                end = val < end ? val : end;
+            }
+            break;
+        }
+        case Expression::Kind::kRelGT: {
+            // if c >= 5, the range pair should be (5, max)
+            // if c > 5, the range pair should be (6, max)
+            auto v = OptimizerUtils::boundValue(col, BVO::GREATER_THAN, val);
+            CHECK_BOUND_VALUE(v, col.get_name());
+            // where c > 1 and c > 2 , 2 should be valid.
+            if (begin == Value()) {
+                begin = v;
+            } else {
+                begin = v < begin ? begin : v;
+            }
+            break;
+        }
+        default:
+            return Status::SemanticError();
+    }
+    return Status::OK();
+}
+
+IndexScan* IndexScanRule::cloneIndexScan(graph::QueryContext *qctx,
+                                         const OptGroupExpr *groupExpr) const {
+    auto in = static_cast<const IndexScan *>(groupExpr->node());
+    auto ctx = std::make_unique<std::vector<storage::cpp2::IndexQueryContext>>();
+    auto returnCols = std::make_unique<std::vector<std::string>>(*in->returnColumns());
+    auto indexScan = IndexScan::make(qctx,
+                                     nullptr,
+                                     in->space(),
+                                     std::move(ctx),
+                                     std::move(returnCols),
+                                     in->isEdge(),
+                                     in->schemaId());
+    return indexScan;
+}
+
+bool IndexScanRule::isEdge(const OptGroupExpr *groupExpr) const {
+    auto in = static_cast<const IndexScan *>(groupExpr->node());
+    return in->isEdge();
+}
+
+int32_t IndexScanRule::schemaId(const OptGroupExpr *groupExpr) const {
+    auto in = static_cast<const IndexScan *>(groupExpr->node());
+    return in->schemaId();
+}
+
+GraphSpaceID IndexScanRule::spaceId(const OptGroupExpr *groupExpr) const {
+    auto in = static_cast<const IndexScan *>(groupExpr->node());
+    return in->space();
+}
+
+std::unique_ptr<Expression>
+IndexScanRule::filterExpr(const OptGroupExpr *groupExpr) const {
+    auto in = static_cast<const IndexScan *>(groupExpr->node());
+    auto qct = in->queryContext();
+    // The initial IndexScan plan node has only one queryContext.
+    if (qct->size() != 1) {
+        LOG(ERROR) << "Index Scan plan node error";
+        return nullptr;
+    }
+    return Expression::decode(qct->begin()->get_filter());
+}
+
+Status IndexScanRule::analyzeExpression(Expression* expr,
+                                        FilterItems* items,
+                                        ScanKind* kind,
+                                        bool isEdge) const {
+    // TODO (sky) : Currently only simple logical expressions are supported,
+    //              such as all AND or all OR expressions, example :
+    //              where c1 > 1 and c1 < 2 and c2 == 1
+    //              where c1 == 1 or c2 == 1 or c3 == 1
+    //
+    //              Hybrid logical expressions are not supported yet, example :
+    //              where c1 > 1 and c2 >1 or c3 > 1
+    //              where c1 > 1 and c1 < 2 or c2 > 1
+    //              where c1 < 1 and (c2 == 1 or c2 == 2)
+    switch (expr->kind()) {
+        case Expression::Kind::kLogicalOr :
+        case Expression::Kind::kLogicalAnd : {
+            auto lExpr = static_cast<LogicalExpression*>(expr);
+            auto k = expr->kind() == Expression::Kind::kLogicalAnd
+                     ? ScanKind::Kind::LOGICAL_AND : ScanKind::Kind::LOGICAL_OR;
+            if (kind->getKind() == ScanKind::Kind::UNKNOWN) {
+                kind->setKind(k);
+            } else if (kind->getKind() != k) {
+                auto errorMsg = folly::StringPiece("Condition not support yet : %s",
+                                                   Expression::encode(*expr).c_str());
+                return Status::NotSupported(errorMsg);
+            }
+            auto ret = analyzeExpression(lExpr->left(), items, kind, isEdge);
+            NG_RETURN_IF_ERROR(ret);
+            ret = analyzeExpression(lExpr->right(), items, kind, isEdge);
+            NG_RETURN_IF_ERROR(ret);
+            break;
+        }
+        case Expression::Kind::kRelLE:
+        case Expression::Kind::kRelGE:
+        case Expression::Kind::kRelEQ:
+        case Expression::Kind::kRelLT:
+        case Expression::Kind::kRelGT:
+        case Expression::Kind::kRelNE: {
+            auto* rExpr = static_cast<RelationalExpression*>(expr);
+            auto ret = isEdge
+                       ? addFilterItem<EdgePropertyExpression>(rExpr, items)
+                       : addFilterItem<TagPropertyExpression>(rExpr, items);
+            NG_RETURN_IF_ERROR(ret);
+            break;
+        }
+        default: {
+            auto errorMsg = folly::StringPiece("Filter not support yet : %s",
+                                               Expression::encode(*expr).c_str());
+            return Status::NotSupported(errorMsg);
+        }
+    }
+    return Status::OK();
+}
+
+template <typename E, typename>
+Status IndexScanRule::addFilterItem(RelationalExpression* expr, FilterItems* items) const {
+    // TODO (sky) : Check illegal filter. for example : where c1 == 1 and c1 == 2
+    auto relType = std::is_same<E, EdgePropertyExpression>::value
+                   ? Expression::Kind::kEdgeProperty
+                   : Expression::Kind::kTagProperty;
+    graph::QueryExpressionContext ctx(nullptr);
+    if (expr->left()->kind() == relType &&
+        expr->right()->kind() == Expression::Kind::kConstant) {
+        auto* l = static_cast<const E*>(expr->left());
+        auto* r = static_cast<ConstantExpression*>(expr->right());
+        items->addItem(*l->prop(), expr->kind(), r->eval(ctx));
+    } else if (expr->left()->kind() == Expression::Kind::kConstant &&
+               expr->right()->kind() == relType) {
+        auto* r = static_cast<const E*>(expr->right());
+        auto* l = static_cast<ConstantExpression*>(expr->left());
+        items->addItem(*r->prop(), reverseRelationalExprKind(expr->kind()), l->eval(ctx));
+    } else {
+        return Status::Error("Optimizer error, when rewrite relational expression");
+    }
+
+    return Status::OK();
+}
+
+Expression::Kind IndexScanRule::reverseRelationalExprKind(Expression::Kind kind) const {
+    switch (kind) {
+        case Expression::Kind::kRelGE: {
+            return Expression::Kind::kRelLE;
+        }
+        case Expression::Kind::kRelGT: {
+            return Expression::Kind::kRelLT;
+        }
+        case Expression::Kind::kRelLE: {
+            return Expression::Kind::kRelGE;
+        }
+        case Expression::Kind::kRelLT: {
+            return Expression::Kind::kRelGT;
+        }
+        default: {
+            return kind;
+        }
+    }
+}
+
+IndexItem IndexScanRule::findOptimalIndex(graph::QueryContext *qctx,
+                                          const OptGroupExpr *groupExpr,
+                                          const FilterItems& items) const {
+    // The rule of priority is '==' --> '< > <= >=' --> '!='
+    // Step 1 : find out all valid indexes for where condition.
+    auto validIndexes = findValidIndex(qctx, groupExpr, items);
+    if (validIndexes.empty()) {
+        LOG(ERROR) << "No valid index found";
+        return nullptr;
+    }
+    // Step 2 : find optimal indexes for equal condition.
+    auto indexesEq = findIndexForEqualScan(validIndexes, items);
+    if (indexesEq.size() == 1) {
+        return indexesEq[0];
+    }
+    // Step 3 : find optimal indexes for range condition.
+    auto indexesRange = findIndexForRangeScan(indexesEq, items);
+
+    // At this stage, all the optimizations are done.
+    // Because the storage layer only needs one. So return first one of indexesRange.
+    return indexesRange[0];
+}
+
+std::vector<IndexItem>
+IndexScanRule::allIndexesBySchema(graph::QueryContext *qctx,
+                                  const OptGroupExpr *groupExpr) const {
+    auto ret = isEdge(groupExpr)
+               ? qctx->getMetaClient()->getEdgeIndexesFromCache(spaceId(groupExpr))
+               : qctx->getMetaClient()->getTagIndexesFromCache(spaceId(groupExpr));
+    if (!ret.ok()) {
+        LOG(ERROR) << "No index was found";
+        return {};
+    }
+    std::vector<IndexItem> indexes;
+    for (auto& index : ret.value()) {
+        // TODO (sky) : ignore rebuilding indexes
+        auto id = isEdge(groupExpr)
+                  ? index->get_schema_id().get_edge_type()
+                  : index->get_schema_id().get_tag_id();
+        if (id == schemaId(groupExpr)) {
+            indexes.emplace_back(index);
+        }
+    }
+    if (indexes.empty()) {
+        LOG(ERROR) << "No index was found";
+        return {};
+    }
+    return indexes;
+}
+
+std::vector<IndexItem>
+IndexScanRule::findValidIndex(graph::QueryContext *qctx,
+                              const OptGroupExpr *groupExpr,
+                              const FilterItems& items) const {
+    auto indexes = allIndexesBySchema(qctx, groupExpr);
+    if (indexes.empty()) {
+        return indexes;
+    }
+    std::vector<IndexItem> validIndexes;
+    // Find indexes for match all fields by where condition.
+    for (const auto& index : indexes) {
+        bool allColsHint = true;
+        const auto& fields = index->get_fields();
+        for (const auto& item : items.items) {
+            auto it = std::find_if(fields.begin(), fields.end(),
+                                   [item](const auto &field) {
+                                       return field.get_name() == item.col_;
+                                   });
+            if (it == fields.end()) {
+                allColsHint = false;
+                break;
+            }
+        }
+        if (allColsHint) {
+            validIndexes.emplace_back(index);
+        }
+    }
+    // If the first field of the index does not match any condition, the index is invalid.
+    // remove it from validIndexes.
+    if (!validIndexes.empty()) {
+        auto index = validIndexes.begin();
+        while (index != validIndexes.end()) {
+            const auto& fields = index->get()->get_fields();
+            auto it = std::find_if(items.items.begin(), items.items.end(),
+                                   [fields](const auto &item) {
+                                       return item.col_ == fields[0].get_name();
+                                   });
+            if (it == items.items.end()) {
+                validIndexes.erase(index);
+            } else {
+                index++;
+            }
+        }
+    }
+    return validIndexes;
+}
+
+std::vector<IndexItem>
+IndexScanRule::findIndexForEqualScan(const std::vector<IndexItem>& indexes,
+                                     const FilterItems& items) const {
+    std::vector<std::pair<int32_t, IndexItem>> eqIndexHint;
+    for (auto& index : indexes) {
+        int32_t hintCount = 0;
+        for (const auto& field : index->get_fields()) {
+            auto it = std::find_if(items.items.begin(), items.items.end(),
+                                   [field](const auto &item) {
+                                       return item.col_ == field.get_name();
+                                   });
+            if (it == items.items.end()) {
+                break;
+            }
+            if (it->relOP_ == RelationalExpression::Kind::kRelEQ) {
+                ++hintCount;
+            } else {
+                break;
+            }
+        }
+        eqIndexHint.emplace_back(hintCount, index);
+    }
+    // Sort the priorityIdxs for equivalent condition.
+    std::vector<IndexItem> priorityIdxs;
+    auto comp = [] (std::pair<int32_t, IndexItem>& lhs,
+                    std::pair<int32_t, IndexItem>& rhs) {
+        return lhs.first > rhs.first;
+    };
+    std::sort(eqIndexHint.begin(), eqIndexHint.end(), comp);
+    // Get the index with the highest hit rate from eqIndexHint.
+    int32_t maxHint = eqIndexHint[0].first;
+    for (const auto& hint : eqIndexHint) {
+        if (hint.first < maxHint) {
+            break;
+        }
+        priorityIdxs.emplace_back(hint.second);
+    }
+    return priorityIdxs;
+}
+
+std::vector<IndexItem>
+IndexScanRule::findIndexForRangeScan(const std::vector<IndexItem>& indexes,
+                                     const FilterItems& items) const {
+    std::map<int32_t, IndexItem> rangeIndexHint;
+    for (const auto& index : indexes) {
+        int32_t hintCount = 0;
+        for (const auto& field : index->get_fields()) {
+            auto fi = std::find_if(items.items.begin(), items.items.end(),
+                                   [field](const auto &item) {
+                                       return item.col_ == field.get_name();
+                                   });
+            if (fi == items.items.end()) {
+                break;
+            }
+            if ((*fi).relOP_ == RelationalExpression::Kind::kRelEQ) {
+                continue;
+            }
+            if ((*fi).relOP_ == RelationalExpression::Kind::kRelGE ||
+                (*fi).relOP_ == RelationalExpression::Kind::kRelGT ||
+                (*fi).relOP_ == RelationalExpression::Kind::kRelLE ||
+                (*fi).relOP_ == RelationalExpression::Kind::kRelLT) {
+                hintCount++;
+            } else {
+                break;
+            }
+        }
+        rangeIndexHint[hintCount] = index;
+    }
+    std::vector<IndexItem> priorityIdxs;
+    int32_t maxHint = rangeIndexHint.rbegin()->first;
+    for (auto iter = rangeIndexHint.rbegin(); iter != rangeIndexHint.rend(); iter++) {
+        if (iter->first < maxHint) {
+            break;
+        }
+        priorityIdxs.emplace_back(iter->second);
+    }
+    return priorityIdxs;
+}
+
+}   // namespace opt
+}   // namespace nebula
diff --git a/src/optimizer/rule/IndexScanRule.h b/src/optimizer/rule/IndexScanRule.h
new file mode 100644
index 00000000..aa1c9fed
--- /dev/null
+++ b/src/optimizer/rule/IndexScanRule.h
@@ -0,0 +1,174 @@
+/* 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 OPTIMIZER_INDEXSCANRULE_H_
+#define OPTIMIZER_INDEXSCANRULE_H_
+
+#include "optimizer/OptRule.h"
+#include "planner/Query.h"
+#include "optimizer/OptimizerUtils.h"
+
+namespace nebula {
+namespace graph {
+class IndexScan;
+}   // namespace graph
+namespace opt {
+
+using graph::IndexScan;
+using graph::OptimizerUtils;
+using graph::PlanNode;
+using graph::QueryContext;
+using storage::cpp2::IndexQueryContext;
+using storage::cpp2::IndexColumnHint;
+using BVO = graph::OptimizerUtils::BoundValueOperator;
+using IndexItem = std::shared_ptr<meta::cpp2::IndexItem>;
+using IndexQueryCtx = std::unique_ptr<std::vector<IndexQueryContext>>;
+
+class IndexScanRule final : public OptRule {
+    FRIEND_TEST(IndexScanRuleTest, BoundValueTest);
+    FRIEND_TEST(IndexScanRuleTest, IQCtxTest);
+
+public:
+    static std::unique_ptr<OptRule> kInstance;
+
+    bool match(const OptGroupExpr *groupExpr) const override;
+
+    Status transform(graph::QueryContext *qctx,
+                     const OptGroupExpr *groupExpr,
+                     TransformResult *result) const override;
+
+    std::string toString() const override;
+
+private:
+    struct ScanKind {
+        enum class Kind {
+            UNKNOWN = 0,
+            LOGICAL_OR,
+            LOGICAL_AND,
+        };
+
+    private:
+        Kind kind_;
+
+    public:
+        ScanKind() {
+            kind_ = Kind::UNKNOWN;
+        }
+        void setKind(Kind k) {
+            kind_ = k;
+        }
+        Kind getKind() {
+            return kind_;
+        }
+        bool isLogicalAnd() {
+            return kind_ == Kind::LOGICAL_AND;
+        }
+    };
+
+    // col_   : index column name
+    // relOP_ : Relational operator , for example c1 > 1 , the relOP_ == kRelGT
+    //                                            1 > c1 , the relOP_ == kRelLT
+    // value_ : Constant value. from ConstantExpression.
+    struct FilterItem {
+        std::string                 col_;
+        RelationalExpression::Kind  relOP_;
+        Value                       value_;
+
+        FilterItem(const std::string& col,
+                   RelationalExpression::Kind relOP,
+                   const Value& value)
+                   : col_(col)
+                   , relOP_(relOP)
+                   , value_(value) {}
+    };
+
+    // FilterItems used for optimal index fetch and index scan context optimize.
+    // for example : where c1 > 1 and c1 < 2 , the FilterItems should be :
+    //               {c1, kRelGT, 1} , {c1, kRelLT, 2}
+    struct FilterItems {
+        std::vector<FilterItem> items;
+        FilterItems() {}
+        explicit FilterItems(const std::vector<FilterItem>& i) {
+            items = i;
+        }
+        void addItem(const std::string& field,
+                      RelationalExpression::Kind kind,
+                      const Value& v) {
+            items.emplace_back(FilterItem(field, kind, v));
+        }
+    };
+
+    IndexScanRule();
+
+    Status createIndexQueryCtx(IndexQueryCtx &iqctx,
+                               ScanKind kind,
+                               const FilterItems& items,
+                               graph::QueryContext *qctx,
+                               const OptGroupExpr *groupExpr) const;
+
+    Status createIQCWithLogicAnd(IndexQueryCtx &iqctx,
+                                 const FilterItems& items,
+                                 graph::QueryContext *qctx,
+                                 const OptGroupExpr *groupExpr) const;
+
+    Status createIQCWithLogicOR(IndexQueryCtx &iqctx,
+                                const FilterItems& items,
+                                graph::QueryContext *qctx,
+                                const OptGroupExpr *groupExpr) const;
+
+    Status appendIQCtx(const IndexItem& index,
+                       const FilterItems& items,
+                       IndexQueryCtx &iqctx) const;
+
+    Status appendColHint(std::vector<IndexColumnHint>& hitns,
+                         const FilterItems& items,
+                         const meta::cpp2::ColumnDef& col) const;
+
+    Status boundValue(const FilterItem& item,
+                      const meta::cpp2::ColumnDef& col,
+                      Value& begin, Value& end) const;
+
+    IndexScan* cloneIndexScan(graph::QueryContext *qctx, const OptGroupExpr *groupExpr) const;
+
+    bool isEdge(const OptGroupExpr *groupExpr) const;
+
+    int32_t schemaId(const OptGroupExpr *groupExpr) const;
+
+    GraphSpaceID spaceId(const OptGroupExpr *groupExpr) const;
+
+    std::unique_ptr<Expression> filterExpr(const OptGroupExpr *groupExpr) const;
+
+    Status analyzeExpression(Expression* expr, FilterItems* items,
+                             ScanKind* kind, bool isEdge) const;
+
+    template <typename E,
+              typename = std::enable_if_t<std::is_same<E, EdgePropertyExpression>::value ||
+                                          std::is_same<E, TagPropertyExpression>::value>>
+    Status addFilterItem(RelationalExpression* expr, FilterItems* items) const;
+
+    Expression::Kind reverseRelationalExprKind(Expression::Kind kind) const;
+
+    IndexItem findOptimalIndex(graph::QueryContext *qctx,
+                               const OptGroupExpr *groupExpr,
+                               const FilterItems& items) const;
+
+    std::vector<IndexItem>
+    allIndexesBySchema(graph::QueryContext *qctx, const OptGroupExpr *groupExpr) const;
+
+    std::vector<IndexItem> findValidIndex(graph::QueryContext *qctx,
+                                          const OptGroupExpr *groupExpr,
+                                          const FilterItems& items) const;
+
+    std::vector<IndexItem> findIndexForEqualScan(const std::vector<IndexItem>& indexes,
+                                                 const FilterItems& items) const;
+
+    std::vector<IndexItem> findIndexForRangeScan(const std::vector<IndexItem>& indexes,
+                                                 const FilterItems& items) const;
+};
+
+}   // namespace opt
+}   // namespace nebula
+
+#endif   // OPTIMIZER_INDEXSCANRULE_H_
diff --git a/src/optimizer/test/CMakeLists.txt b/src/optimizer/test/CMakeLists.txt
index 771f9f97..fcc235b5 100644
--- a/src/optimizer/test/CMakeLists.txt
+++ b/src/optimizer/test/CMakeLists.txt
@@ -1,43 +1,52 @@
+# 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.
+#
+
+set(OPTIMIZER_TEST_LIB
+    $<TARGET_OBJECTS:common_base_obj>
+    $<TARGET_OBJECTS:common_concurrent_obj>
+    $<TARGET_OBJECTS:common_datatypes_obj>
+    $<TARGET_OBJECTS:common_expression_obj>
+    $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_time_obj>
+    $<TARGET_OBJECTS:common_time_function_obj>
+    $<TARGET_OBJECTS:common_meta_thrift_obj>
+    $<TARGET_OBJECTS:common_meta_client_obj>
+    $<TARGET_OBJECTS:common_meta_obj>
+    $<TARGET_OBJECTS:common_storage_thrift_obj>
+    $<TARGET_OBJECTS:common_graph_thrift_obj>
+    $<TARGET_OBJECTS:common_conf_obj>
+    $<TARGET_OBJECTS:common_fs_obj>
+    $<TARGET_OBJECTS:common_thrift_obj>
+    $<TARGET_OBJECTS:common_common_thrift_obj>
+    $<TARGET_OBJECTS:common_thread_obj>
+    $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
+    $<TARGET_OBJECTS:common_charset_obj>
+    $<TARGET_OBJECTS:common_encryption_obj>
+    $<TARGET_OBJECTS:common_http_client_obj>
+    $<TARGET_OBJECTS:common_process_obj>
+    $<TARGET_OBJECTS:common_agg_function_obj>
+    $<TARGET_OBJECTS:idgenerator_obj>
+    $<TARGET_OBJECTS:expr_visitor_obj>
+    $<TARGET_OBJECTS:session_obj>
+    $<TARGET_OBJECTS:graph_auth_obj>
+    $<TARGET_OBJECTS:graph_flags_obj>
+    $<TARGET_OBJECTS:util_obj>
+    $<TARGET_OBJECTS:planner_obj>
+    $<TARGET_OBJECTS:parser_obj>
+    $<TARGET_OBJECTS:context_obj>
+    $<TARGET_OBJECTS:validator_obj>
+    $<TARGET_OBJECTS:optimizer_obj>
+)
 nebula_add_test(
     NAME
         index_bound_value_test
     SOURCES
         IndexBoundValueTest.cpp
     OBJECTS
-        $<TARGET_OBJECTS:common_base_obj>
-        $<TARGET_OBJECTS:common_concurrent_obj>
-        $<TARGET_OBJECTS:common_datatypes_obj>
-        $<TARGET_OBJECTS:common_expression_obj>
-        $<TARGET_OBJECTS:common_function_manager_obj>
-        $<TARGET_OBJECTS:common_time_obj>
-        $<TARGET_OBJECTS:common_time_function_obj>
-        $<TARGET_OBJECTS:common_meta_thrift_obj>
-        $<TARGET_OBJECTS:common_meta_client_obj>
-        $<TARGET_OBJECTS:common_meta_obj>
-        $<TARGET_OBJECTS:common_storage_thrift_obj>
-        $<TARGET_OBJECTS:common_graph_thrift_obj>
-        $<TARGET_OBJECTS:common_conf_obj>
-        $<TARGET_OBJECTS:common_fs_obj>
-        $<TARGET_OBJECTS:common_thrift_obj>
-        $<TARGET_OBJECTS:common_common_thrift_obj>
-        $<TARGET_OBJECTS:common_thread_obj>
-        $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
-        $<TARGET_OBJECTS:common_charset_obj>
-        $<TARGET_OBJECTS:common_encryption_obj>
-        $<TARGET_OBJECTS:common_http_client_obj>
-        $<TARGET_OBJECTS:common_process_obj>
-        $<TARGET_OBJECTS:common_agg_function_obj>
-        $<TARGET_OBJECTS:idgenerator_obj>
-        $<TARGET_OBJECTS:expr_visitor_obj>
-        $<TARGET_OBJECTS:session_obj>
-        $<TARGET_OBJECTS:graph_auth_obj>
-        $<TARGET_OBJECTS:graph_flags_obj>
-        $<TARGET_OBJECTS:util_obj>
-        $<TARGET_OBJECTS:planner_obj>
-        $<TARGET_OBJECTS:parser_obj>
-        $<TARGET_OBJECTS:context_obj>
-        $<TARGET_OBJECTS:validator_obj>
-        $<TARGET_OBJECTS:optimizer_obj>
+        ${OPTIMIZER_TEST_LIB}
     LIBRARIES
         proxygenhttpserver
         proxygenlib
@@ -46,3 +55,19 @@ nebula_add_test(
         gtest
         gtest_main
 )
+
+nebula_add_test(
+    NAME
+        index_scan_rule_test
+    SOURCES
+        IndexScanRuleTest.cpp
+    OBJECTS
+        ${OPTIMIZER_TEST_LIB}
+    LIBRARIES
+        proxygenhttpserver
+        proxygenlib
+    ${THRIFT_LIBRARIES}
+        wangle
+        gtest
+        gtest_main
+)
diff --git a/src/optimizer/test/IndexScanRuleTest.cpp b/src/optimizer/test/IndexScanRuleTest.cpp
new file mode 100644
index 00000000..947340a1
--- /dev/null
+++ b/src/optimizer/test/IndexScanRuleTest.cpp
@@ -0,0 +1,227 @@
+/* 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 "optimizer/OptimizerUtils.h"
+#include "optimizer/rule/IndexScanRule.h"
+
+namespace nebula {
+namespace opt {
+
+TEST(IndexScanRuleTest, BoundValueTest) {
+    meta::cpp2::ColumnDef col;
+    auto* inst = std::move(IndexScanRule::kInstance).get();
+    auto* instance = static_cast<IndexScanRule*>(inst);
+    IndexScanRule::FilterItems items;
+    {
+        Value begin, end;
+        col.set_name("col1");
+        col.type.set_type(meta::cpp2::PropertyType::INT64);
+        // col > 1 and col < 5
+        items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(1L));
+        items.addItem("col1", RelationalExpression::Kind::kRelLT, Value(5L));
+        for (const auto& item : items.items) {
+            auto ret = instance->boundValue(item, col, begin, end);
+            ASSERT_TRUE(ret.ok());
+        }
+        // Expect begin = 2 , end = 5;
+        EXPECT_EQ((Value(2L)), begin);
+        EXPECT_EQ((Value(5L)), end);
+    }
+    {
+        Value begin, end;
+        items.items.clear();
+        col.set_name("col1");
+        col.type.set_type(meta::cpp2::PropertyType::INT64);
+        // col > 1 and col > 6
+        items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(1L));
+        items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(6L));
+        for (const auto& item : items.items) {
+            auto ret = instance->boundValue(item, col, begin, end);
+            ASSERT_TRUE(ret.ok());
+        }
+        // Expect begin = 7
+        EXPECT_EQ(Value(7L), begin);
+        EXPECT_EQ(Value(), end);
+    }
+    {
+        Value begin, end;
+        items.items.clear();
+        col.set_name("col1");
+        col.type.set_type(meta::cpp2::PropertyType::INT64);
+        // col > 1 and col >= 6
+        items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(1L));
+        items.addItem("col1", RelationalExpression::Kind::kRelGE, Value(6L));
+        for (const auto& item : items.items) {
+            auto ret = instance->boundValue(item, col, begin, end);
+            ASSERT_TRUE(ret.ok());
+        }
+        // Expect begin = 6
+        EXPECT_EQ(Value(6L), begin);
+        EXPECT_EQ(Value(), end);
+    }
+    {
+        Value begin, end;
+        items.items.clear();
+        col.set_name("col1");
+        col.type.set_type(meta::cpp2::PropertyType::INT64);
+        // col < 1 and col <= 6
+        items.addItem("col1", RelationalExpression::Kind::kRelLT, Value(1L));
+        items.addItem("col1", RelationalExpression::Kind::kRelLE, Value(6L));
+        for (const auto& item : items.items) {
+            auto ret = instance->boundValue(item, col, begin, end);
+            ASSERT_TRUE(ret.ok());
+        }
+        // Expect end = 1
+        EXPECT_EQ(Value(1L), end);
+        EXPECT_EQ(Value(), begin);
+    }
+}
+
+TEST(IndexScanRuleTest, IQCtxTest) {
+    auto* inst = std::move(IndexScanRule::kInstance).get();
+    auto* instance = static_cast<IndexScanRule*>(inst);
+    {
+        IndexItem index = std::make_unique<meta::cpp2::IndexItem>();
+        IndexScanRule::FilterItems items;
+        IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>();
+        auto ret = instance->appendIQCtx(index, items, iqctx);
+        ASSERT_TRUE(ret.ok());
+    }
+    {
+        IndexItem index = std::make_unique<meta::cpp2::IndexItem>();
+        IndexScanRule::FilterItems items;
+        IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>();
+        // setup index
+        {
+            std::vector<meta::cpp2::ColumnDef> cols;
+            for (int8_t i = 0; i < 5; i++) {
+                meta::cpp2::ColumnDef col;
+                col.set_name(folly::stringPrintf("col%d", i));
+                col.type.set_type(meta::cpp2::PropertyType::INT64);
+                cols.emplace_back(std::move(col));
+            }
+            index->set_fields(std::move(cols));
+            index->set_index_id(1);
+        }
+        // setup FilterItems col0 < 1 and col0 <= 6
+        {
+            items.items.clear();
+            items.addItem("col0", RelationalExpression::Kind::kRelLT, Value(1L));
+            items.addItem("col0", RelationalExpression::Kind::kRelLE, Value(6L));
+
+            auto ret = instance->appendIQCtx(index, items, iqctx);
+            ASSERT_TRUE(ret.ok());
+
+            ASSERT_EQ(1, iqctx->size());
+            ASSERT_EQ(1, (iqctx.get()->begin())->get_column_hints().size());
+            ASSERT_EQ(1, (iqctx.get()->begin())->get_index_id());
+            ASSERT_EQ("", (iqctx.get()->begin())->get_filter());
+            const auto& colHints = (iqctx.get()->begin())->get_column_hints();
+            ASSERT_EQ("col0", colHints.begin()->get_column_name());
+            ASSERT_EQ(storage::cpp2::ScanType::RANGE, colHints.begin()->get_scan_type());
+            ASSERT_EQ(Value(std::numeric_limits<int64_t>::min()),
+                      colHints.begin()->get_begin_value());
+            ASSERT_EQ(Value(1L), colHints.begin()->get_end_value());
+        }
+
+        // setup FilterItems col0 > 1 and col1 <= 2 and col1 > -1 and col2 > 3
+        //                   and col3 < 4 and col4 == 4
+        {
+            items.items.clear();
+            iqctx.get()->clear();
+            items.addItem("col0", RelationalExpression::Kind::kRelGT, Value(1L));
+            items.addItem("col1", RelationalExpression::Kind::kRelLE, Value(2L));
+            items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(-1L));
+            items.addItem("col2", RelationalExpression::Kind::kRelGT, Value(3L));
+            items.addItem("col3", RelationalExpression::Kind::kRelLT, Value(4L));
+            items.addItem("col4", RelationalExpression::Kind::kRelEQ, Value(4L));
+
+            auto ret = instance->appendIQCtx(index, items, iqctx);
+            ASSERT_TRUE(ret.ok());
+
+            ASSERT_EQ(1, iqctx->size());
+            ASSERT_EQ(5, (iqctx.get()->begin())->get_column_hints().size());
+            ASSERT_EQ(1, (iqctx.get()->begin())->get_index_id());
+            ASSERT_EQ("", (iqctx.get()->begin())->get_filter());
+            const auto& colHints = (iqctx.get()->begin())->get_column_hints();
+            {
+                auto hint = colHints[0];
+                ASSERT_EQ("col0", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(2L), hint.get_begin_value());
+                ASSERT_EQ(Value(std::numeric_limits<int64_t>::max()), hint.get_end_value());
+            }
+            {
+                auto hint = colHints[1];
+                ASSERT_EQ("col1", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(0L), hint.get_begin_value());
+                ASSERT_EQ(Value(3L), hint.get_end_value());
+            }
+            {
+                auto hint = colHints[2];
+                ASSERT_EQ("col2", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(4L), hint.get_begin_value());
+                ASSERT_EQ(Value(std::numeric_limits<int64_t>::max()), hint.get_end_value());
+            }
+            {
+                auto hint = colHints[3];
+                ASSERT_EQ("col3", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(std::numeric_limits<int64_t>::min()), hint.get_begin_value());
+                ASSERT_EQ(Value(4L), hint.get_end_value());
+            }
+            {
+                auto hint = colHints[4];
+                ASSERT_EQ("col4", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::PREFIX, hint.get_scan_type());
+                ASSERT_EQ(Value(4L), hint.get_begin_value());
+            }
+        }
+
+        // setup FilterItems col0 > 1 and col1 <= 2 and col1 > -1 and col2 != 3
+        //                   and col3 < 4
+        // only expect col0 and col1 exits in column hints.
+        // col2 and col3 should be filter in storage layer.
+        {
+            items.items.clear();
+            iqctx.get()->clear();
+            items.addItem("col0", RelationalExpression::Kind::kRelGT, Value(1L));
+            items.addItem("col1", RelationalExpression::Kind::kRelLE, Value(2L));
+            items.addItem("col1", RelationalExpression::Kind::kRelGT, Value(-1L));
+            items.addItem("col2", RelationalExpression::Kind::kRelNE, Value(3L));
+            items.addItem("col3", RelationalExpression::Kind::kRelLT, Value(4L));
+
+            auto ret = instance->appendIQCtx(index, items, iqctx);
+            ASSERT_TRUE(ret.ok());
+
+            ASSERT_EQ(1, iqctx->size());
+            ASSERT_EQ(2, (iqctx.get()->begin())->get_column_hints().size());
+            ASSERT_EQ(1, (iqctx.get()->begin())->get_index_id());
+            ASSERT_EQ("", (iqctx.get()->begin())->get_filter());
+            const auto& colHints = (iqctx.get()->begin())->get_column_hints();
+            {
+                auto hint = colHints[0];
+                ASSERT_EQ("col0", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(2L), hint.get_begin_value());
+                ASSERT_EQ(Value(std::numeric_limits<int64_t>::max()), hint.get_end_value());
+            }
+            {
+                auto hint = colHints[1];
+                ASSERT_EQ("col1", hint.get_column_name());
+                ASSERT_EQ(storage::cpp2::ScanType::RANGE, hint.get_scan_type());
+                ASSERT_EQ(Value(0L), hint.get_begin_value());
+                ASSERT_EQ(Value(3L), hint.get_end_value());
+            }
+        }
+    }
+}
+
+}   // namespace opt
+}   // namespace nebula
diff --git a/src/planner/Query.h b/src/planner/Query.h
index 169c25b4..5cf4072c 100644
--- a/src/planner/Query.h
+++ b/src/planner/Query.h
@@ -448,7 +448,7 @@ public:
         return schemaId_;
     }
 
-    void setQueryContext(IndexQueryCtx contexts) {
+    void setIndexQueryContext(IndexQueryCtx contexts) {
         contexts_ = std::move(contexts);
     }
 
diff --git a/src/validator/IndexScanValidator.cpp b/src/validator/IndexScanValidator.cpp
index 82ab24a3..4f6848f7 100644
--- a/src/validator/IndexScanValidator.cpp
+++ b/src/validator/IndexScanValidator.cpp
@@ -31,22 +31,24 @@ Status IndexScanValidator::prepareFrom() {
     auto *sentence = static_cast<const LookupSentence *>(sentence_);
     spaceId_ = vctx_->whichSpace().id;
     const auto* from = sentence->from();
-    auto ret = qctx_->schemaMng()->toEdgeType(spaceId_, *from);
-    if (ret.ok()) {
-        isEdge_ = true;
-    } else {
-        ret = qctx_->schemaMng()->toTagID(spaceId_, *from);
-        NG_RETURN_IF_ERROR(ret);
-        isEdge_ = false;
+    auto ret = qctx_->schemaMng()->getSchemaIDByName(spaceId_, *from);
+    if (!ret.ok()) {
+        return ret.status();
     }
-    schemaId_ = ret.value();
+    isEdge_ = ret.value().first;
+    schemaId_ = ret.value().second;
     return Status::OK();
 }
 
 Status IndexScanValidator::prepareYield() {
     auto *sentence = static_cast<const LookupSentence *>(sentence_);
+    if (sentence->yieldClause() == nullptr) {
+        return Status::OK();
+    }
     auto columns = sentence->yieldClause()->columns();
-    auto schema = qctx_->schemaMng()->getEdgeSchema(spaceId_, schemaId_);
+    auto schema = isEdge_
+                  ? qctx_->schemaMng()->getEdgeSchema(spaceId_, schemaId_)
+                  : qctx_->schemaMng()->getTagSchema(spaceId_, schemaId_);
     const auto* from = sentence->from();
     if (schema == nullptr) {
         return isEdge_
@@ -54,26 +56,26 @@ Status IndexScanValidator::prepareYield() {
                : Status::TagNotFound("Tag schema not found : %s", from->c_str());
     }
     returnCols_ = std::make_unique<std::vector<std::string>>();
-    for (const auto* col : columns) {
+    for (auto col : columns) {
+        std::string schemaName, colName;
         if (col->expr()->kind() == Expression::Kind::kLabelAttribute) {
-            auto laExpr = static_cast<LabelAttributeExpression *>(col->expr());
-            if (*laExpr->left()->name() != *from) {
-                return Status::SemanticError("Schema name error : %s",
-                                             laExpr->left()->name()->c_str());
-            }
-            auto ret = schema->getFieldType(*laExpr->right()->name());
-            if (ret == meta::cpp2::PropertyType::UNKNOWN) {
-                return Status::SemanticError("Column %s not found in schema %s",
-                                             laExpr->right()->name()->c_str(),
-                                             from->c_str());
-            }
-            returnCols_->emplace_back(*laExpr->right()->name());
-            auto typeResult = deduceExprType(col->expr());
-            NG_RETURN_IF_ERROR(typeResult);
-            outputs_.emplace_back(deduceColName(col), typeResult.value());
+            auto la = static_cast<LabelAttributeExpression *>(col->expr());
+            schemaName = *la->left()->name();
+            colName = *la->right()->name();
         } else {
-            return Status::SemanticError();
+            return Status::SemanticError("Yield clauses are not supported : %s",
+                                         col->expr()->toString().c_str());
+        }
+
+        if (schemaName != *from) {
+            return Status::SemanticError("Schema name error : %s", schemaName.c_str());
         }
+        auto ret = schema->getFieldType(colName);
+        if (ret == meta::cpp2::PropertyType::UNKNOWN) {
+            return Status::SemanticError("Column %s not found in schema %s",
+                                         colName.c_str(), from->c_str());
+        }
+        returnCols_->emplace_back(colName);
     }
     return Status::OK();
 }
@@ -110,30 +112,7 @@ Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from
         case Expression::Kind::kRelGT:
         case Expression::Kind::kRelNE: {
             auto* rExpr = static_cast<RelationalExpression*>(expr);
-            auto* left = rExpr->left();
-            auto* right = rExpr->right();
-            // Does not support filter : schema.col1 > schema.col2
-            if (left->kind() == Expression::Kind::kLabelAttribute &&
-                right->kind() == Expression::Kind::kLabelAttribute) {
-                return Status::NotSupported("Expression %s not supported yet",
-                                            rExpr->toString().c_str());
-            } else if (left->kind() == Expression::Kind::kLabelAttribute) {
-                auto* attExpr = static_cast<LabelAttributeExpression *>(left);
-                if (*attExpr->left()->name() != from) {
-                    return Status::SemanticError("Schema name error : %s",
-                                                 attExpr->left()->name()->c_str());
-                }
-            } else if (right->kind() == Expression::Kind::kLabelAttribute) {
-                auto* attExpr = static_cast<LabelAttributeExpression *>(right);
-                if (*attExpr->left()->name() != from) {
-                    return Status::SemanticError("Schema name error : %s",
-                                                  attExpr->left()->name()->c_str());
-                }
-            } else {
-                return Status::NotSupported("Expression %s not supported yet",
-                                            rExpr->toString().c_str());
-            }
-            break;
+            return checkRelExpr(rExpr, from);
         }
         default: {
             return Status::NotSupported("Expression %s not supported yet",
@@ -143,5 +122,89 @@ Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from
     return Status::OK();
 }
 
+Status IndexScanValidator::checkRelExpr(RelationalExpression* expr,
+                                        const std::string& from) {
+    auto* left = expr->left();
+    auto* right = expr->right();
+    // Does not support filter : schema.col1 > schema.col2
+    if (left->kind() == Expression::Kind::kLabelAttribute &&
+        right->kind() == Expression::Kind::kLabelAttribute) {
+        return Status::NotSupported("Expression %s not supported yet",
+                                    expr->toString().c_str());
+    } else if (left->kind() == Expression::Kind::kLabelAttribute ||
+               right->kind() == Expression::Kind::kLabelAttribute) {
+        auto ret = rewriteRelExpr(expr, from);
+        NG_RETURN_IF_ERROR(ret);
+    } else {
+        return Status::NotSupported("Expression %s not supported yet",
+                                    expr->toString().c_str());
+    }
+    return Status::OK();
+}
+
+Status IndexScanValidator::rewriteRelExpr(RelationalExpression* expr,
+                                          const std::string& from) {
+    auto* left = expr->left();
+    auto* right = expr->right();
+    auto leftIsAE = left->kind() == Expression::Kind::kLabelAttribute;
+
+    std::string ref, prop;
+    auto* la = leftIsAE
+               ? static_cast<LabelAttributeExpression *>(left)
+               : static_cast<LabelAttributeExpression *>(right);
+    if (*la->left()->name() != from) {
+        return Status::SemanticError("Schema name error : %s",
+                                     la->left()->name()->c_str());
+    }
+
+    ref = *la->left()->name();
+    prop = *la->right()->name();
+
+    // rewrite ConstantExpression
+    auto c = leftIsAE
+             ? checkConstExpr(right, prop)
+             : checkConstExpr(left, prop);
+
+    if (!c.ok()) {
+        return Status::SemanticError("expression error : %s", left->toString().c_str());
+    }
+
+    if (leftIsAE) {
+        expr->setRight(new ConstantExpression(std::move(c).value()));
+    } else {
+        expr->setLeft(new ConstantExpression(std::move(c).value()));
+    }
+
+    // rewrite PropertyExpression
+    if (leftIsAE) {
+        if (isEdge_) {
+            expr->setLeft(ExpressionUtils::rewriteLabelAttribute<EdgePropertyExpression>(la));
+        } else {
+            expr->setLeft(ExpressionUtils::rewriteLabelAttribute<TagPropertyExpression>(la));
+        }
+    } else {
+        if (isEdge_) {
+            expr->setRight(ExpressionUtils::rewriteLabelAttribute<EdgePropertyExpression>(la));
+        } else {
+            expr->setRight(ExpressionUtils::rewriteLabelAttribute<TagPropertyExpression>(la));
+        }
+    }
+    return Status::OK();
+}
+
+StatusOr<Value> IndexScanValidator::checkConstExpr(Expression* expr,
+                                                   const std::string& prop) {
+    auto schema = isEdge_
+                  ? qctx_->schemaMng()->getEdgeSchema(spaceId_, schemaId_)
+                  : qctx_->schemaMng()->getTagSchema(spaceId_, schemaId_);
+    auto type = schema->getFieldType(prop);
+    QueryExpressionContext dummy(nullptr);
+    auto v = Expression::eval(expr, dummy);
+    if (v.type() != SchemaUtil::propTypeToValueType(type)) {
+        return Status::SemanticError("Column type error : %s", prop.c_str());
+    }
+    return v;
+}
+
 }   // namespace graph
 }   // namespace nebula
diff --git a/src/validator/IndexScanValidator.h b/src/validator/IndexScanValidator.h
index 437c7a69..a8e686eb 100644
--- a/src/validator/IndexScanValidator.h
+++ b/src/validator/IndexScanValidator.h
@@ -34,6 +34,12 @@ private:
 
     Status checkFilter(Expression* expr, const std::string& from);
 
+    Status checkRelExpr(RelationalExpression* expr, const std::string& from);
+
+    Status rewriteRelExpr(RelationalExpression* expr, const std::string& from);
+
+    StatusOr<Value> checkConstExpr(Expression* expr, const std::string& prop);
+
 private:
     GraphSpaceID               spaceId_{0};
     IndexScan::IndexQueryCtx   contexts_{};
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index f54d58b1..5b5a8223 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -36,6 +36,7 @@
 #include "visitor/DeducePropsVisitor.h"
 #include "visitor/DeduceTypeVisitor.h"
 #include "visitor/EvaluableExprVisitor.h"
+#include "validator/IndexScanValidator.h"
 
 namespace nebula {
 namespace graph {
@@ -188,9 +189,10 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon
             return std::make_unique<RebuildEdgeIndexValidator>(sentence, context);
         case Sentence::Kind::kDropEdgeIndex:
             return std::make_unique<DropEdgeIndexValidator>(sentence, context);
+        case Sentence::Kind::kLookup:
+            return std::make_unique<IndexScanValidator>(sentence, context);
         case Sentence::Kind::kMatch:
         case Sentence::Kind::kUnknown:
-        case Sentence::Kind::kLookup:
         case Sentence::Kind::kDownload:
         case Sentence::Kind::kIngest:
         case Sentence::Kind::kReturn: {
-- 
GitLab