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