diff --git a/src/context/Iterator.h b/src/context/Iterator.h index 8f0f22f69f4da0217c7c7340fcdd8e3f6aac25e1..e17f8b4b2fe2d5c1907c1a2ae0dbd8a45e390d89 100644 --- a/src/context/Iterator.h +++ b/src/context/Iterator.h @@ -116,17 +116,15 @@ public: // The derived class should rewrite get prop if the Value is kind of dataset. virtual const Value& getColumn(const std::string& col) const = 0; - virtual const Value& getTagProp(const std::string& tag, - const std::string& prop) const { - UNUSED(tag); - UNUSED(prop); + virtual const Value& getTagProp(const std::string&, + const std::string&) const { + DLOG(FATAL) << "Shouldn't call the unimplemented method"; return Value::kEmpty; } - virtual const Value& getEdgeProp(const std::string& edge, - const std::string& prop) const { - UNUSED(edge); - UNUSED(prop); + virtual const Value& getEdgeProp(const std::string&, + const std::string&) const { + DLOG(FATAL) << "Shouldn't call the unimplemented method"; return Value::kEmpty; } diff --git a/src/exec/query/GetPropExecutor.h b/src/exec/query/GetPropExecutor.h index 18e4413e54f7ca0f2ca80569554a663fe1ce344f..6c6ec88585baebd00bb01ad91bfdec48c6cbb7f9 100644 --- a/src/exec/query/GetPropExecutor.h +++ b/src/exec/query/GetPropExecutor.h @@ -4,7 +4,8 @@ * attached with Common Clause Condition 1.0, found in the LICENSES directory. */ -#pragma once +#ifndef _EXEC_QUERY_GET_PROP_EXECUTOR_H_ +#define _EXEC_QUERY_GET_PROP_EXECUTOR_H_ #include "exec/Executor.h" #include "common/clients/storage/StorageClientBase.h" @@ -69,3 +70,5 @@ protected: } // namespace graph } // namespace nebula + +#endif // _EXEC_QUERY_GET_PROP_EXECUTOR_H_ diff --git a/src/exec/query/GetVerticesExecutor.cpp b/src/exec/query/GetVerticesExecutor.cpp index 2eabc4795efc72ef8feed6d8e3023412510ea442..26549f502bd643183c1a748b4d02c77d908b8e66 100644 --- a/src/exec/query/GetVerticesExecutor.cpp +++ b/src/exec/query/GetVerticesExecutor.cpp @@ -32,15 +32,13 @@ folly::Future<Status> GetVerticesExecutor::getVertices() { nebula::DataSet vertices({kVid}); std::unordered_set<Value> uniqueVid; if (!gv->vertices().empty()) { + // TODO(shylock) not dedup in here, do it when generate plan for (auto& v : gv->vertices()) { auto ret = uniqueVid.emplace(v.values.front()); if (ret.second) { vertices.emplace_back(std::move(v)); } } - vertices.rows.insert(vertices.rows.end(), - std::make_move_iterator(gv->vertices().begin()), - std::make_move_iterator(gv->vertices().end())); } if (gv->src() != nullptr) { // Accept Table such as | $a | $b | $c |... as input which one column indicate src diff --git a/src/exec/query/test/FilterTest.cpp b/src/exec/query/test/FilterTest.cpp index 064e30410a19807ae7a816757863ee057ff1b906..7eb381dff91f1341957ec14f7b6f4463021d3402 100644 --- a/src/exec/query/test/FilterTest.cpp +++ b/src/exec/query/test/FilterTest.cpp @@ -6,10 +6,11 @@ #include <gtest/gtest.h> #include "context/QueryContext.h" -#include "planner/Query.h" #include "exec/query/FilterExecutor.h" -#include "exec/query/test/QueryTestBase.h" #include "exec/query/ProjectExecutor.h" +#include "exec/query/test/QueryTestBase.h" +#include "planner/Query.h" +#include "util/ExpressionUtils.h" namespace nebula { namespace graph { @@ -21,32 +22,49 @@ public: } }; -#define FILTER_RESUTL_CHECK(inputName, outputName, sentence, expected) \ - do { \ - auto* plan = qctx_->plan(); \ - auto yieldSentence = getYieldSentence(sentence); \ - auto* filterNode = \ - Filter::make(plan, nullptr, yieldSentence->where()->filter()); \ - filterNode->setInputVar(inputName); \ - filterNode->setOutputVar(outputName); \ - auto filterExec = \ - std::make_unique<FilterExecutor>(filterNode, qctx_.get()); \ - EXPECT_TRUE(filterExec->execute().get().ok()); \ - auto& filterResult = qctx_->ectx()->getResult(filterNode->varName()); \ - EXPECT_EQ(filterResult.state(), Result::State::kSuccess); \ - \ - filterNode->setInputVar(outputName); \ - auto* project = \ - Project::make(plan, nullptr, yieldSentence->yieldColumns()); \ - project->setInputVar(filterNode->varName()); \ - project->setColNames(std::vector<std::string>{"name"}); \ - \ - auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get()); \ - EXPECT_TRUE(proExe->execute().get().ok()); \ - auto& proSesult = qctx_->ectx()->getResult(project->varName()); \ - \ - EXPECT_EQ(proSesult.value().getDataSet(), expected); \ - EXPECT_EQ(proSesult.state(), Result::State::kSuccess); \ +#define FILTER_RESUTL_CHECK(inputName, outputName, sentence, expected) \ + do { \ + auto* plan = qctx_->plan(); \ + auto yieldSentence = getYieldSentence(sentence); \ + auto columns = yieldSentence->columns(); \ + for (auto& col : columns) { \ + if (col->expr()->kind() == Expression::Kind::kSymProperty) { \ + auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr()); \ + col->setExpr( \ + ExpressionUtils ::transSymbolPropertyExpression<EdgePropertyExpression>( \ + symbolExpr)); \ + } else { \ + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr()); \ + } \ + } \ + auto* filter = yieldSentence->where()->filter(); \ + if (filter->kind() == Expression::Kind::kSymProperty) { \ + auto symbolExpr = static_cast<SymbolPropertyExpression*>(filter); \ + yieldSentence->where()->setFilter( \ + ExpressionUtils ::transSymbolPropertyExpression<EdgePropertyExpression>( \ + symbolExpr)); \ + } else { \ + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(filter); \ + } \ + auto* filterNode = Filter::make(plan, nullptr, yieldSentence->where()->filter()); \ + filterNode->setInputVar(inputName); \ + filterNode->setOutputVar(outputName); \ + auto filterExec = std::make_unique<FilterExecutor>(filterNode, qctx_.get()); \ + EXPECT_TRUE(filterExec->execute().get().ok()); \ + auto& filterResult = qctx_->ectx()->getResult(filterNode->varName()); \ + EXPECT_EQ(filterResult.state(), Result::State::kSuccess); \ + \ + filterNode->setInputVar(outputName); \ + auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns()); \ + project->setInputVar(filterNode->varName()); \ + project->setColNames(std::vector<std::string>{"name"}); \ + \ + auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get()); \ + EXPECT_TRUE(proExe->execute().get().ok()); \ + auto& proSesult = qctx_->ectx()->getResult(project->varName()); \ + \ + EXPECT_EQ(proSesult.value().getDataSet(), expected); \ + EXPECT_EQ(proSesult.state(), Result::State::kSuccess); \ } while (false) TEST_F(FilterTest, TestGetNeighbors_src_dst) { @@ -72,10 +90,8 @@ TEST_F(FilterTest, TestSequential) { TEST_F(FilterTest, TestNullValue) { DataSet expected({"name"}); - FILTER_RESUTL_CHECK("input_sequential", - "filter_sequential", - "YIELD $-.v_name AS name WHERE NULL", - expected); + FILTER_RESUTL_CHECK( + "input_sequential", "filter_sequential", "YIELD $-.v_name AS name WHERE NULL", expected); } TEST_F(FilterTest, TestEmpty) { @@ -85,5 +101,5 @@ TEST_F(FilterTest, TestEmpty) { "YIELD $^.person.name AS name WHERE study.start_year >= 2010", expected); } -} // namespace graph -} // namespace nebula +} // namespace graph +} // namespace nebula diff --git a/src/exec/query/test/LimitTest.cpp b/src/exec/query/test/LimitTest.cpp index 7e33ecd61b0a8217487da2bd0ff7f9fb33497750..3ab2c9a54af4f857cccae10ba8e1b1ab0578a5ec 100644 --- a/src/exec/query/test/LimitTest.cpp +++ b/src/exec/query/test/LimitTest.cpp @@ -6,6 +6,7 @@ #include <gtest/gtest.h> +#include "util/ExpressionUtils.h" #include "context/QueryContext.h" #include "planner/Query.h" #include "exec/query/LimitExecutor.h" @@ -17,26 +18,37 @@ namespace graph { class LimitTest : public QueryTestBase { }; -#define LIMIT_RESUTL_CHECK(outputName, offset, count, expected) \ - do { \ - auto* plan = qctx_->plan(); \ - auto* limitNode = Limit::make(plan, nullptr, offset, count); \ - limitNode->setInputVar("input_neighbor"); \ - limitNode->setOutputVar(outputName); \ - auto limitExec = std::make_unique<LimitExecutor>(limitNode, qctx_.get()); \ - EXPECT_TRUE(limitExec->execute().get().ok()); \ - auto& limitResult = qctx_->ectx()->getResult(limitNode->varName()); \ - EXPECT_EQ(limitResult.state(), Result::State::kSuccess); \ - auto yieldSentence = getYieldSentence( \ - "YIELD study._dst AS name, study.start_year AS start"); \ - auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns()); \ - project->setInputVar(limitNode->varName()); \ - project->setColNames(std::vector<std::string>{"name", "start"}); \ - auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get()); \ - EXPECT_TRUE(proExe->execute().get().ok()); \ - auto& proResult = qctx_->ectx()->getResult(project->varName()); \ - EXPECT_EQ(proResult.value().getDataSet(), expected); \ - EXPECT_EQ(proResult.state(), Result::State::kSuccess); \ +#define LIMIT_RESUTL_CHECK(outputName, offset, count, expected) \ + do { \ + auto* plan = qctx_->plan(); \ + auto* limitNode = Limit::make(plan, nullptr, offset, count); \ + limitNode->setInputVar("input_neighbor"); \ + limitNode->setOutputVar(outputName); \ + auto limitExec = std::make_unique<LimitExecutor>(limitNode, qctx_.get()); \ + EXPECT_TRUE(limitExec->execute().get().ok()); \ + auto& limitResult = qctx_->ectx()->getResult(limitNode->varName()); \ + EXPECT_EQ(limitResult.state(), Result::State::kSuccess); \ + auto yieldSentence = getYieldSentence( \ + "YIELD study._dst AS name, study.start_year AS start"); \ + auto columns = yieldSentence->columns(); \ + for (auto& col : columns) { \ + if (col->expr()->kind() == Expression::Kind::kSymProperty) { \ + auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr()); \ + col->setExpr(ExpressionUtils \ + ::transSymbolPropertyExpression<EdgePropertyExpression>(symbolExpr)); \ + } else { \ + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>( \ + col->expr()); \ + } \ + } \ + auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns()); \ + project->setInputVar(limitNode->varName()); \ + project->setColNames(std::vector<std::string>{"name", "start"}); \ + auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get()); \ + EXPECT_TRUE(proExe->execute().get().ok()); \ + auto& proResult = qctx_->ectx()->getResult(project->varName()); \ + EXPECT_EQ(proResult.value().getDataSet(), expected); \ + EXPECT_EQ(proResult.state(), Result::State::kSuccess); \ } while (false) diff --git a/src/parser/Clauses.cpp b/src/parser/Clauses.cpp index 9d5f263dc7c6926a17374039e34dcbdc18ca4c4e..85829799910da00efc6b0976e3f1eb110e510ded 100644 --- a/src/parser/Clauses.cpp +++ b/src/parser/Clauses.cpp @@ -155,21 +155,19 @@ std::string YieldColumns::toString() const { } bool operator==(const YieldColumn &l, const YieldColumn &r) { - if (l.alias() == nullptr && r.alias() == nullptr) { - } else if (l.alias() != nullptr && r.alias() != nullptr) { + if (l.alias() != nullptr && r.alias() != nullptr) { if (*l.alias() != *r.alias()) { return false; } - } else { + } else if (l.alias() != r.alias()) { return false; } - if (l.expr() == nullptr && r.expr() == nullptr) { - } else if (l.expr() != nullptr && r.expr() != nullptr) { + if (l.expr() != nullptr && r.expr() != nullptr) { if (*l.expr() != *r.expr()) { return false; } - } else { + } else if (l.expr() != r.expr()) { return false; } diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h index fdec0ca2bea9dcd4d9c640e4b06124fb377ebde6..6946241fe1b5788e4782c66f74860b821b829806 100644 --- a/src/parser/Clauses.h +++ b/src/parser/Clauses.h @@ -9,6 +9,7 @@ #include "common/base/Base.h" #include "common/expression/Expression.h" #include "common/interface/gen-cpp2/storage_types.h" +#include "util/ExpressionUtils.h" namespace nebula { class StepClause final { @@ -203,6 +204,10 @@ public: return filter_.get(); } + void setFilter(Expression* expr) { + filter_.reset(expr); + } + std::string toString() const; private: @@ -218,10 +223,30 @@ public: alias_.reset(alias); } + std::unique_ptr<YieldColumn> clone() const { + auto col = std::make_unique<YieldColumn>( + graph::ExpressionUtils::clone(expr_.get()).release()); + if (alias_ != nullptr) { + col->setAlias(new std::string(*alias_)); + } + if (aggFunName_ != nullptr) { + col->setAggFunction(new std::string(*aggFunName_)); + } + return col; + } + + void setExpr(Expression* expr) { + expr_.reset(expr); + } + Expression* expr() const { return expr_.get(); } + void setAlias(std::string* alias) { + alias_.reset(alias); + } + std::string* alias() const { return alias_.get(); } diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h index 75bdfb103921b83fe77c5f60dfec183bedb6d63c..e9a06fad044dbfb13deebac916f66320b1927ae1 100644 --- a/src/parser/TraverseSentences.h +++ b/src/parser/TraverseSentences.h @@ -56,10 +56,18 @@ public: return whereClause_.get(); } + WhereClause* whereClause() { + return whereClause_.get(); + } + const YieldClause* yieldClause() const { return yieldClause_.get(); } + YieldClause* yieldClause() { + return yieldClause_.get(); + } + std::string toString() const override; private: diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 6489c8b6eddd7d846c97cedb0e819c068b8608b1..99f8ca2d09f96e5796b47a1e23182860852d4156 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -424,7 +424,11 @@ var_ref_expression alias_ref_expression : name_label DOT name_label { - $$ = new EdgePropertyExpression($1, $3); + // determine the detail in later stage + $$ = new SymbolPropertyExpression(Expression::Kind::kSymProperty, + new std::string(""), + $1, + $3); } | name_label DOT TYPE_PROP { $$ = new EdgeTypeExpression($1); diff --git a/src/util/ExpressionUtils.h b/src/util/ExpressionUtils.h new file mode 100644 index 0000000000000000000000000000000000000000..ede22afbc4621f9e04121286c4b99944fbc57ffb --- /dev/null +++ b/src/util/ExpressionUtils.h @@ -0,0 +1,337 @@ +/* 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 _UTIL_EXPRESSION_UTILS_H_ +#define _UTIL_EXPRESSION_UTILS_H_ + +#include "common/expression/BinaryExpression.h" +#include "common/expression/Expression.h" +#include "common/expression/FunctionCallExpression.h" +#include "common/expression/SymbolPropertyExpression.h" +#include "common/expression/TypeCastingExpression.h" +#include "common/expression/UnaryExpression.h" + +namespace nebula { +namespace graph { + +class ExpressionUtils { +public: + explicit ExpressionUtils(...) = delete; + + // return true for continue, false return directly + using Visitor = std::function<bool(const Expression*)>; + using MutableVisitor = std::function<bool(Expression*)>; + + // preorder traverse in fact for tail call optimization + // if want to do some thing like eval, don't try it + template <typename T, + typename V, + typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, Expression>::value>> + static bool traverse(T* expr, V visitor) { + if (!visitor(expr)) { + return false; + } + switch (expr->kind()) { + case Expression::Kind::kDstProperty: + case Expression::Kind::kSrcProperty: + case Expression::Kind::kTagProperty: + case Expression::Kind::kEdgeProperty: + case Expression::Kind::kEdgeSrc: + case Expression::Kind::kEdgeType: + case Expression::Kind::kEdgeRank: + case Expression::Kind::kEdgeDst: + case Expression::Kind::kInputProperty: + case Expression::Kind::kVarProperty: + case Expression::Kind::kUUID: + case Expression::Kind::kVar: + case Expression::Kind::kVersionedVar: + case Expression::Kind::kSymProperty: + case Expression::Kind::kConstant: { + return true; + } + case Expression::Kind::kAdd: + case Expression::Kind::kMinus: + case Expression::Kind::kMultiply: + case Expression::Kind::kDivision: + case Expression::Kind::kMod: + case Expression::Kind::kRelEQ: + case Expression::Kind::kRelNE: + case Expression::Kind::kRelLT: + case Expression::Kind::kRelLE: + case Expression::Kind::kRelGT: + case Expression::Kind::kRelGE: + case Expression::Kind::kLogicalAnd: + case Expression::Kind::kLogicalOr: + case Expression::Kind::kLogicalXor: { + using ToType = keep_const_t<T, BinaryExpression>; + auto biExpr = static_cast<ToType*>(expr); + if (!traverse(biExpr->left(), visitor)) { + return false; + } + return traverse(biExpr->right(), visitor); + } + case Expression::Kind::kRelIn: + case Expression::Kind::kUnaryIncr: + case Expression::Kind::kUnaryDecr: + case Expression::Kind::kUnaryPlus: + case Expression::Kind::kUnaryNegate: + case Expression::Kind::kUnaryNot: { + using ToType = keep_const_t<T, UnaryExpression>; + auto unaryExpr = static_cast<ToType*>(expr); + return traverse(unaryExpr->operand(), visitor); + } + case Expression::Kind::kTypeCasting: { + using ToType = keep_const_t<T, TypeCastingExpression>; + auto typeCastingExpr = static_cast<ToType*>(expr); + return traverse(typeCastingExpr->operand(), visitor); + } + case Expression::Kind::kFunctionCall: { + using ToType = keep_const_t<T, FunctionCallExpression>; + auto funcExpr = static_cast<ToType*>(expr); + for (auto& arg : funcExpr->args()->args()) { + if (!traverse(arg.get(), visitor)) { + return false; + } + } + return true; + } + } + DLOG(FATAL) << "Impossible expression kind " << static_cast<int>(expr->kind()); + return false; + } + + static inline bool isKindOf(const Expression* expr, + const std::unordered_set<Expression::Kind>& expected) { + return expected.find(expr->kind()) != expected.end(); + } + + // null for not found + static const Expression* findAnyKind(const Expression* self, + const std::unordered_set<Expression::Kind>& expected) { + const Expression* found = nullptr; + traverse(self, [&expected, &found](const Expression* expr) -> bool { + if (isKindOf(expr, expected)) { + found = expr; + return false; // Already find so return now + } + return true; // Not find so continue traverse + }); + return found; + } + + // Find all expression fit any kind + // Empty for not found any one + static std::vector<const Expression*> findAnyKindInAll( + const Expression* self, + const std::unordered_set<Expression::Kind>& expected) { + std::vector<const Expression*> exprs; + traverse(self, [&expected, &exprs](const Expression* expr) -> bool { + if (isKindOf(expr, expected)) { + exprs.emplace_back(expr); + } + return true; // Not return always to traverse entire expression tree + }); + return exprs; + } + + static bool hasAnyKind(const Expression* expr, + const std::unordered_set<Expression::Kind>& expected) { + return findAnyKind(expr, expected) != nullptr; + } + + // Require data from input/variable + static bool hasInput(const Expression* expr) { + return hasAnyKind(expr, + {Expression::Kind::kInputProperty, + Expression::Kind::kVarProperty, + Expression::Kind::kVar, + Expression::Kind::kVersionedVar}); + } + + // require data from graph storage + static const Expression* findStorage(const Expression* expr) { + return findAnyKind(expr, + {Expression::Kind::kSymProperty, + Expression::Kind::kTagProperty, + Expression::Kind::kEdgeProperty, + Expression::Kind::kDstProperty, + Expression::Kind::kSrcProperty, + Expression::Kind::kEdgeSrc, + Expression::Kind::kEdgeType, + Expression::Kind::kEdgeRank, + Expression::Kind::kEdgeDst}); + } + + static std::vector<const Expression*> findAllStorage(const Expression* expr) { + return findAnyKindInAll(expr, + {Expression::Kind::kSymProperty, + Expression::Kind::kTagProperty, + Expression::Kind::kEdgeProperty, + Expression::Kind::kDstProperty, + Expression::Kind::kSrcProperty, + Expression::Kind::kEdgeSrc, + Expression::Kind::kEdgeType, + Expression::Kind::kEdgeRank, + Expression::Kind::kEdgeDst}); + } + + static std::vector<const Expression*> findAllInputVariableProp(const Expression* expr) { + return findAnyKindInAll(expr, + {Expression::Kind::kInputProperty, Expression::Kind::kVarProperty}); + } + + static bool hasStorage(const Expression* expr) { + return findStorage(expr) != nullptr; + } + + static bool isStorage(const Expression* expr) { + return isKindOf(expr, + {Expression::Kind::kSymProperty, + Expression::Kind::kTagProperty, + Expression::Kind::kEdgeProperty, + Expression::Kind::kDstProperty, + Expression::Kind::kSrcProperty, + Expression::Kind::kEdgeSrc, + Expression::Kind::kEdgeType, + Expression::Kind::kEdgeRank, + Expression::Kind::kEdgeDst}); + } + + static bool isConstExpr(const Expression* expr) { + return !hasAnyKind(expr, + {Expression::Kind::kInputProperty, + Expression::Kind::kVarProperty, + Expression::Kind::kVar, + Expression::Kind::kVersionedVar, + + Expression::Kind::kSymProperty, + Expression::Kind::kTagProperty, + Expression::Kind::kEdgeProperty, + Expression::Kind::kDstProperty, + Expression::Kind::kSrcProperty, + Expression::Kind::kEdgeSrc, + Expression::Kind::kEdgeType, + Expression::Kind::kEdgeRank, + Expression::Kind::kEdgeDst}); + } + + // clone expression + static std::unique_ptr<Expression> clone(const Expression* expr) { + // TODO(shylock) optimize + if (expr == nullptr) { + return nullptr; + } + return CHECK_NOTNULL(Expression::decode(expr->encode())); + } + + // determine the detail about symbol property expression + template <typename To, + typename = std::enable_if_t<std::is_same<To, EdgePropertyExpression>::value || + std::is_same<To, TagPropertyExpression>::value>> + static void transAllSymbolPropertyExpr(Expression* expr) { + traverse(expr, [](Expression* current) -> bool { + switch (current->kind()) { + case Expression::Kind::kDstProperty: + case Expression::Kind::kSrcProperty: + case Expression::Kind::kSymProperty: + case Expression::Kind::kTagProperty: + case Expression::Kind::kEdgeProperty: + case Expression::Kind::kEdgeSrc: + case Expression::Kind::kEdgeType: + case Expression::Kind::kEdgeRank: + case Expression::Kind::kEdgeDst: + case Expression::Kind::kInputProperty: + case Expression::Kind::kVarProperty: + case Expression::Kind::kUUID: + case Expression::Kind::kVar: + case Expression::Kind::kVersionedVar: + case Expression::Kind::kConstant: { + return true; + } + case Expression::Kind::kAdd: + case Expression::Kind::kMinus: + case Expression::Kind::kMultiply: + case Expression::Kind::kDivision: + case Expression::Kind::kMod: + case Expression::Kind::kRelEQ: + case Expression::Kind::kRelNE: + case Expression::Kind::kRelLT: + case Expression::Kind::kRelLE: + case Expression::Kind::kRelGT: + case Expression::Kind::kRelGE: + case Expression::Kind::kLogicalAnd: + case Expression::Kind::kLogicalOr: + case Expression::Kind::kLogicalXor: { + auto* biExpr = static_cast<BinaryExpression*>(current); + if (biExpr->left()->kind() == Expression::Kind::kSymProperty) { + auto* symbolExpr = static_cast<SymbolPropertyExpression*>(biExpr->left()); + biExpr->setLeft(transSymbolPropertyExpression<To>(symbolExpr)); + } + if (biExpr->right()->kind() == Expression::Kind::kSymProperty) { + auto* symbolExpr = static_cast<SymbolPropertyExpression*>(biExpr->right()); + biExpr->setRight(transSymbolPropertyExpression<To>(symbolExpr)); + } + return true; + } + case Expression::Kind::kRelIn: + case Expression::Kind::kUnaryIncr: + case Expression::Kind::kUnaryDecr: + case Expression::Kind::kUnaryPlus: + case Expression::Kind::kUnaryNegate: + case Expression::Kind::kUnaryNot: { + auto* unaryExpr = static_cast<UnaryExpression*>(current); + if (unaryExpr->operand()->kind() == Expression::Kind::kSymProperty) { + auto* symbolExpr = + static_cast<SymbolPropertyExpression*>(unaryExpr->operand()); + unaryExpr->setOperand(transSymbolPropertyExpression<To>(symbolExpr)); + } + return true; + } + case Expression::Kind::kTypeCasting: { + auto* typeCastingExpr = static_cast<TypeCastingExpression*>(current); + if (typeCastingExpr->operand()->kind() == Expression::Kind::kSymProperty) { + auto* symbolExpr = + static_cast<SymbolPropertyExpression*>(typeCastingExpr->operand()); + typeCastingExpr->setOperand(transSymbolPropertyExpression<To>(symbolExpr)); + } + return true; + } + case Expression::Kind::kFunctionCall: { + auto* funcExpr = static_cast<FunctionCallExpression*>(current); + for (auto& arg : funcExpr->args()->args()) { + if (arg->kind() == Expression::Kind::kSymProperty) { + auto* symbolExpr = static_cast<SymbolPropertyExpression*>(arg.get()); + arg.reset(transSymbolPropertyExpression<To>(symbolExpr)); + } + } + return true; + } + } // switch + DLOG(FATAL) << "Impossible expression kind " << static_cast<int>(current->kind()); + return false; + }); // traverse + } + + template <typename To, + typename = std::enable_if_t<std::is_same<To, EdgePropertyExpression>::value || + std::is_same<To, TagPropertyExpression>::value>> + static To* transSymbolPropertyExpression(SymbolPropertyExpression* expr) { + return new To(new std::string(std::move(*expr->sym())), + new std::string(std::move(*expr->prop()))); + } + +private: + // keep const or non-const with T + template <typename T, typename To> + using keep_const_t = std::conditional_t<std::is_const<T>::value, + const std::remove_const_t<To>, + std::remove_const_t<To>>; +}; + +} // namespace graph +} // namespace nebula + +#endif // _UTIL_EXPRESSION_UTILS_H_ diff --git a/src/util/test/CMakeLists.txt b/src/util/test/CMakeLists.txt index b6934dd1adf8a007c1bf5c26bf07cda80ffd89ab..ead3eb1bde62b50717e25790e252fcc0c99f7c9f 100644 --- a/src/util/test/CMakeLists.txt +++ b/src/util/test/CMakeLists.txt @@ -1,16 +1,20 @@ nebula_add_test( - NAME - util_test + NAME utils_test SOURCES + ExpressionUtilsTest.cpp IdGeneratorTest.cpp ObjectPoolTest.cpp ScopedTimerTest.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:idgenerator_obj> LIBRARIES gtest gtest_main + ${THRIFT_LIBRARIES} ) diff --git a/src/util/test/ExpressionUtilsTest.cpp b/src/util/test/ExpressionUtilsTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8d2da9da7596e8bec248703b030a1fd86a1ffde --- /dev/null +++ b/src/util/test/ExpressionUtilsTest.cpp @@ -0,0 +1,208 @@ +/* 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 "common/expression/ArithmeticExpression.h" +#include "common/expression/ConstantExpression.h" +#include "common/expression/TypeCastingExpression.h" +#include "util/ExpressionUtils.h" + +namespace nebula { +namespace graph { + +class ExpressionUtilsTest : public ::testing::Test {}; + +TEST_F(ExpressionUtilsTest, CheckComponent) { + { + // single node + const auto root = std::make_unique<ConstantExpression>(); + + ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kConstant})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kConstant})); + + ASSERT_TRUE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kConstant, Expression::Kind::kAdd})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kConstant, Expression::Kind::kAdd})); + + ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kAdd})); + + ASSERT_FALSE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kDivision, Expression::Kind::kAdd})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kAdd})); + + // find + const Expression *found = + ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kConstant}); + ASSERT_EQ(found, root.get()); + + found = ExpressionUtils::findAnyKind( + root.get(), + {Expression::Kind::kConstant, Expression::Kind::kAdd, Expression::Kind::kEdgeProperty}); + ASSERT_EQ(found, root.get()); + + found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kEdgeDst}); + ASSERT_EQ(found, nullptr); + + found = ExpressionUtils::findAnyKind( + root.get(), {Expression::Kind::kEdgeRank, Expression::Kind::kInputProperty}); + ASSERT_EQ(found, nullptr); + + // find all + const auto willFoundAll = std::vector<const Expression *>{root.get()}; + std::vector<const Expression *> founds = + ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant}); + ASSERT_EQ(founds, willFoundAll); + + founds = ExpressionUtils::findAnyKindInAll( + root.get(), + {Expression::Kind::kAdd, Expression::Kind::kConstant, Expression::Kind::kEdgeDst}); + ASSERT_EQ(founds, willFoundAll); + + founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kSrcProperty}); + ASSERT_TRUE(founds.empty()); + + founds = ExpressionUtils::findAnyKindInAll(root.get(), + {Expression::Kind::kUnaryNegate, + Expression::Kind::kEdgeDst, + Expression::Kind::kEdgeDst}); + ASSERT_TRUE(founds.empty()); + } + + { + // list like + const auto root = std::make_unique<TypeCastingExpression>( + Value::Type::BOOL, + new TypeCastingExpression( + Value::Type::BOOL, + new TypeCastingExpression(Value::Type::BOOL, new ConstantExpression()))); + + ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kTypeCasting})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kConstant})); + + ASSERT_TRUE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd})); + + ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kAdd})); + + ASSERT_FALSE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kDivision, Expression::Kind::kAdd})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kAdd})); + + // found + const Expression *found = + ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kTypeCasting}); + ASSERT_EQ(found, root.get()); + + found = ExpressionUtils::findAnyKind(root.get(), + {Expression::Kind::kFunctionCall, + Expression::Kind::kTypeCasting, + Expression::Kind::kLogicalAnd}); + ASSERT_EQ(found, root.get()); + + found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kDivision}); + ASSERT_EQ(found, nullptr); + + found = ExpressionUtils::findAnyKind(root.get(), + {Expression::Kind::kLogicalXor, + Expression::Kind::kRelGE, + Expression::Kind::kEdgeProperty}); + ASSERT_EQ(found, nullptr); + + // found all + std::vector<const Expression *> founds = + ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant}); + ASSERT_EQ(founds.size(), 1); + + founds = ExpressionUtils::findAnyKindInAll( + root.get(), {Expression::Kind::kFunctionCall, Expression::Kind::kTypeCasting}); + ASSERT_EQ(founds.size(), 3); + + founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kAdd}); + ASSERT_TRUE(founds.empty()); + + founds = ExpressionUtils::findAnyKindInAll( + root.get(), {Expression::Kind::kRelLE, Expression::Kind::kDstProperty}); + ASSERT_TRUE(founds.empty()); + } + + { + // tree like + const auto root = std::make_unique<ArithmeticExpression>( + Expression::Kind::kAdd, + new ArithmeticExpression(Expression::Kind::kDivision, + new ConstantExpression(3), + new ArithmeticExpression(Expression::Kind::kMinus, + new ConstantExpression(4), + new ConstantExpression(2))), + new ArithmeticExpression(Expression::Kind::kMod, + new ArithmeticExpression(Expression::Kind::kMultiply, + new ConstantExpression(3), + new ConstantExpression(10)), + new ConstantExpression(2))); + + ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kMinus})); + + ASSERT_TRUE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd})); + ASSERT_TRUE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kSymProperty, Expression::Kind::kDivision})); + + ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kConstant})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kFunctionCall})); + + ASSERT_FALSE(ExpressionUtils::isKindOf( + root.get(), {Expression::Kind::kDivision, Expression::Kind::kEdgeProperty})); + ASSERT_FALSE(ExpressionUtils::hasAnyKind( + root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kLogicalAnd})); + + // found + const Expression *found = + ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kAdd}); + ASSERT_EQ(found, root.get()); + + found = ExpressionUtils::findAnyKind(root.get(), + {Expression::Kind::kFunctionCall, + Expression::Kind::kRelLE, + Expression::Kind::kMultiply}); + ASSERT_NE(found, nullptr); + + found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kInputProperty}); + ASSERT_EQ(found, nullptr); + + found = ExpressionUtils::findAnyKind(root.get(), + {Expression::Kind::kLogicalXor, + Expression::Kind::kEdgeRank, + Expression::Kind::kUnaryNot}); + ASSERT_EQ(found, nullptr); + + // found all + std::vector<const Expression *> founds = + ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant}); + ASSERT_EQ(founds.size(), 6); + + founds = ExpressionUtils::findAnyKindInAll( + root.get(), {Expression::Kind::kDivision, Expression::Kind::kMinus}); + ASSERT_EQ(founds.size(), 2); + + founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kEdgeDst}); + ASSERT_TRUE(founds.empty()); + + founds = ExpressionUtils::findAnyKindInAll( + root.get(), {Expression::Kind::kLogicalAnd, Expression::Kind::kUnaryNegate}); + ASSERT_TRUE(founds.empty()); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/CMakeLists.txt b/src/validator/CMakeLists.txt index 2d141df336342d349a68149eff136eb8fdb5f77d..daa47c3df699f96c5c38816b02236274aa5aff58 100644 --- a/src/validator/CMakeLists.txt +++ b/src/validator/CMakeLists.txt @@ -17,6 +17,8 @@ nebula_add_library( AdminValidator.cpp MaintainValidator.cpp MutateValidator.cpp + FetchEdgesValidator.cpp + FetchVerticesValidator.cpp LimitValidator.cpp OrderByValidator.cpp YieldValidator.cpp diff --git a/src/validator/FetchEdgesValidator.cpp b/src/validator/FetchEdgesValidator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..caefb9f89c3dc24973637ec3881e2c3a7829b595 --- /dev/null +++ b/src/validator/FetchEdgesValidator.cpp @@ -0,0 +1,245 @@ +/* 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 "validator/FetchEdgesValidator.h" +#include "planner/Query.h" +#include "util/ExpressionUtils.h" +#include "util/SchemaUtil.h" + +namespace nebula { +namespace graph { + +/*static*/ const std::unordered_set<std::string> FetchEdgesValidator::reservedProperties{ + kSrc, + kType, + kRank, + kDst, +}; + +Status FetchEdgesValidator::validateImpl() { + NG_RETURN_IF_ERROR(check()); + NG_RETURN_IF_ERROR(prepareEdges()); + NG_RETURN_IF_ERROR(prepareProperties()); + return Status::OK(); +} + +Status FetchEdgesValidator::toPlan() { + // Start [-> some input] -> GetEdges [-> Project] [-> Dedup] [-> next stage] -> End + auto *plan = qctx_->plan(); + auto *getEdgesNode = GetEdges::make(plan, + nullptr, + spaceId_, + std::move(edges_), + std::move(src_), + edgeType_, + std::move(ranking_), + std::move(dst_), + std::move(props_), + std::move(exprs_), + dedup_, + limit_, + std::move(orderBy_), + std::move(filter_)); + getEdgesNode->setInputVar(inputVar_); + // the pipe will set the input variable + PlanNode *current = getEdgesNode; + + if (withProject_) { + auto *projectNode = Project::make(plan, current, newYield_->yields()); + projectNode->setInputVar(current->varName()); + projectNode->setColNames(colNames_); + current = projectNode; + } + // Project select the properties then dedup + if (dedup_) { + auto *dedupNode = Dedup::make(plan, current); + dedupNode->setInputVar(current->varName()); + dedupNode->setColNames(colNames_); + current = dedupNode; + + // the framework will add data collect to collect the result + // if the result is required + } + root_ = current; + tail_ = getEdgesNode; + return Status::OK(); +} + +Status FetchEdgesValidator::check() { + auto *sentence = static_cast<FetchEdgesSentence *>(sentence_); + spaceId_ = vctx_->whichSpace().id; + edgeTypeName_ = *sentence->edge(); + auto edgeStatus = qctx_->schemaMng()->toEdgeType(spaceId_, edgeTypeName_); + NG_RETURN_IF_ERROR(edgeStatus); + edgeType_ = edgeStatus.value(); + schema_ = qctx_->schemaMng()->getEdgeSchema(spaceId_, edgeType_); + if (schema_ == nullptr) { + LOG(ERROR) << "No schema found for " << sentence->edge(); + return Status::Error("No schema found for `%s'", sentence->edge()->c_str()); + } + + return Status::OK(); +} + +Status FetchEdgesValidator::prepareEdges() { + auto *sentence = static_cast<FetchEdgesSentence *>(sentence_); + // from ref, eval in execute + if (sentence->isRef()) { + src_ = sentence->ref()->srcid(); + auto result = checkRef(src_, Value::Type::STRING); + NG_RETURN_IF_ERROR(result); + inputVar_ = std::move(result).value(); + ranking_ = sentence->ref()->rank(); + result = checkRef(ranking_, Value::Type::INT); + NG_RETURN_IF_ERROR(result); + if (inputVar_ != result.value()) { + return Status::Error("Can't refer to different variable as key at same time."); + } + dst_ = sentence->ref()->dstid(); + result = checkRef(dst_, Value::Type::STRING); + NG_RETURN_IF_ERROR(result); + if (inputVar_ != result.value()) { + return Status::Error("Can't refer to different variable as key at same time."); + } + return Status::OK(); + } + + // from constant, eval now + QueryExpressionContext dummy(nullptr); + auto keysPointer = sentence->keys(); + if (keysPointer != nullptr) { + auto keys = keysPointer->keys(); + // row: _src, _type, _ranking, _dst + edges_.reserve(keys.size()); + for (const auto &key : keys) { + DCHECK(ExpressionUtils::isConstExpr(key->srcid())); + // TODO(shylock) Add new value type EDGE_ID to semantic and simplify this + auto src = key->srcid()->eval(dummy); + if (!src.isStr()) { // string as vid + return Status::NotSupported("src is not a vertex id"); + } + auto ranking = key->rank(); + DCHECK(ExpressionUtils::isConstExpr(key->dstid())); + auto dst = key->dstid()->eval(dummy); + if (!src.isStr()) { + return Status::NotSupported("dst is not a vertex id"); + } + edges_.emplace_back(nebula::Row( + {std::move(src).getStr(), edgeType_, ranking, std::move(dst).getStr()})); + } + } + return Status::OK(); +} + +Status FetchEdgesValidator::prepareProperties() { + auto *sentence = static_cast<FetchEdgesSentence *>(sentence_); + auto *yield = sentence->yieldClause(); + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType_); + // empty for all properties + if (yield != nullptr) { + withProject_ = true; + // insert the reserved properties expression be compatible with 1.0 + auto *newYieldColumns = new YieldColumns(); + newYieldColumns->addColumn( + new YieldColumn(new EdgeSrcIdExpression(new std::string(edgeTypeName_)))); + newYieldColumns->addColumn( + new YieldColumn(new EdgeDstIdExpression(new std::string(edgeTypeName_)))); + newYieldColumns->addColumn( + new YieldColumn(new EdgeRankExpression(new std::string(edgeTypeName_)))); + for (auto col : yield->columns()) { + newYieldColumns->addColumn(col->clone().release()); + } + newYield_ = qctx_->objPool()->add(new YieldClause(newYieldColumns, yield->isDistinct())); + + std::vector<std::string> propsName; + propsName.reserve(newYield_->columns().size()); + dedup_ = newYield_->isDistinct(); + for (auto col : newYield_->columns()) { + if (col->expr()->kind() == Expression::Kind::kSymProperty) { + auto symbolExpr = static_cast<SymbolPropertyExpression *>(col->expr()); + col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>( + symbolExpr)); + } else { + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr()); + } + const auto *invalidExpr = findInvalidYieldExpression(col->expr()); + if (invalidExpr != nullptr) { + return Status::Error("Invalid newYield_ expression `%s'.", + col->expr()->toString().c_str()); + } + // The properties from storage directly push down only + // The other will be computed in Project Executor + const auto storageExprs = ExpressionUtils::findAllStorage(col->expr()); + for (const auto &storageExpr : storageExprs) { + const auto *expr = static_cast<const SymbolPropertyExpression *>(storageExpr); + if (*expr->sym() != edgeTypeName_) { + return Status::Error("Mismatched edge type name"); + } + // Check is prop name in schema + if (schema_->getFieldIndex(*expr->prop()) < 0 && + reservedProperties.find(*expr->prop()) == reservedProperties.end()) { + LOG(ERROR) << "Unknown column `" << *expr->prop() << "' in edge `" + << edgeTypeName_ << "'."; + return Status::Error("Unknown column `%s' in edge `%s'", + expr->prop()->c_str(), + edgeTypeName_.c_str()); + } + propsName.emplace_back(*expr->prop()); + } + // TODO(shylock) think about the push-down expr + } + prop.set_props(std::move(propsName)); + + // outpus + colNames_ = deduceColNames(newYield_->yields()); + outputs_.reserve(colNames_.size()); + for (std::size_t i = 0; i < colNames_.size(); ++i) { + auto typeResult = deduceExprType(newYield_->columns()[i]->expr()); + NG_RETURN_IF_ERROR(typeResult); + outputs_.emplace_back(colNames_[i], typeResult.value()); + } + } else { + // no yield + std::vector<std::string> propNames; // filter the type + propNames.reserve(3 + schema_->getNumFields()); + outputs_.reserve(3 + schema_->getNumFields()); + colNames_.reserve(3 + schema_->getNumFields()); + // insert the reserved properties be compatible with 1.0 + propNames.emplace_back(kSrc); + outputs_.emplace_back(edgeTypeName_ + "." + kSrc, Value::Type::STRING); + colNames_.emplace_back(edgeTypeName_ + "." + kSrc); + propNames.emplace_back(kDst); + outputs_.emplace_back(edgeTypeName_ + "." + kDst, Value::Type::STRING); + colNames_.emplace_back(edgeTypeName_ + "." + kDst); + propNames.emplace_back(kRank); + outputs_.emplace_back(edgeTypeName_ + "." + kRank, Value::Type::INT); + colNames_.emplace_back(edgeTypeName_ + "." + kRank); + + for (std::size_t i = 0; i < schema_->getNumFields(); ++i) { + propNames.emplace_back(schema_->getFieldName(i)); + outputs_.emplace_back(schema_->getFieldName(i), + SchemaUtil::propTypeToValueType(schema_->getFieldType(i))); + colNames_.emplace_back(schema_->getFieldName(i)); + } + prop.set_props(std::move(propNames)); + } + + props_.emplace_back(std::move(prop)); + return Status::OK(); +} + +/*static*/ +const Expression *FetchEdgesValidator::findInvalidYieldExpression(const Expression *root) { + return ExpressionUtils::findAnyKind(root, + {Expression::Kind::kInputProperty, + Expression::Kind::kVarProperty, + Expression::Kind::kSrcProperty, + Expression::Kind::kDstProperty}); +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/FetchEdgesValidator.h b/src/validator/FetchEdgesValidator.h new file mode 100644 index 0000000000000000000000000000000000000000..da6f54c147ee45b77dbe76c6cf673d0a297e50b2 --- /dev/null +++ b/src/validator/FetchEdgesValidator.h @@ -0,0 +1,67 @@ +/* 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 _VALIDATOR_FETCH_EDGES_VALIDATOR_H_ +#define _VALIDATOR_FETCH_EDGES_VALIDATOR_H_ + +#include "common/base/Base.h" +#include "common/interface/gen-cpp2/storage_types.h" +#include "parser/TraverseSentences.h" +#include "validator/Validator.h" + +namespace nebula { +namespace graph { + +class FetchEdgesValidator final : public Validator { +public: + FetchEdgesValidator(Sentence* sentence, QueryContext* context) + : Validator(sentence, context) {} + +private: + Status validateImpl() override; + + Status toPlan() override; + + Status check(); + + Status prepareEdges(); + + Status prepareProperties(); + + static const Expression* findInvalidYieldExpression(const Expression* root); + + static const std::unordered_set<std::string> reservedProperties; + +private: + GraphSpaceID spaceId_; + std::vector<nebula::Row> edges_; + Expression* src_{nullptr}; + Expression* ranking_{nullptr}; + Expression* dst_{nullptr}; + std::string edgeTypeName_; + EdgeType edgeType_{0}; + std::shared_ptr<const meta::SchemaProviderIf> schema_; + std::vector<storage::cpp2::EdgeProp> props_; + std::vector<storage::cpp2::Expr> exprs_; + bool dedup_{false}; + int64_t limit_{std::numeric_limits<int64_t>::max()}; + std::vector<storage::cpp2::OrderBy> orderBy_{}; + std::string filter_{""}; + // valid when yield expression not require storage + // So expression like these will be evaluate in Project Executor + bool withProject_{false}; + // outputs + std::vector<std::string> colNames_; + // new yield to inject reserved properties for compatible with 1.0 + YieldClause* newYield_{nullptr}; + // input + std::string inputVar_; +}; + +} // namespace graph +} // namespace nebula + +#endif // _VALIDATOR_FETCH_EDGES_VALIDATOR_H_ diff --git a/src/validator/FetchVerticesValidator.cpp b/src/validator/FetchVerticesValidator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f4ee57b56e2f8b60851257c5a73fd6fb1dfdace9 --- /dev/null +++ b/src/validator/FetchVerticesValidator.cpp @@ -0,0 +1,223 @@ +/* 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 "validator/FetchVerticesValidator.h" +#include "planner/Query.h" +#include "util/ExpressionUtils.h" +#include "util/SchemaUtil.h" + +namespace nebula { +namespace graph { + +Status FetchVerticesValidator::validateImpl() { + NG_RETURN_IF_ERROR(check()); + NG_RETURN_IF_ERROR(prepareVertices()); + NG_RETURN_IF_ERROR(prepareProperties()); + return Status::OK(); +} + +Status FetchVerticesValidator::toPlan() { + // Start [-> some input] -> GetVertices [-> Project] [-> Dedup] [-> next stage] -> End + auto *sentence = static_cast<FetchVerticesSentence*>(sentence_); + auto *plan = qctx_->plan(); + auto *getVerticesNode = GetVertices::make(plan, + nullptr, + spaceId_, + std::move(vertices_), + std::move(src_), + std::move(props_), + std::move(exprs_), + dedup_, + std::move(orderBy_), + limit_, + std::move(filter_)); + getVerticesNode->setInputVar(inputVar_); + // pipe will set the input variable + PlanNode *current = getVerticesNode; + + if (withProject_) { + auto *projectNode = Project::make(plan, current, sentence->yieldClause()->yields()); + projectNode->setInputVar(current->varName()); + projectNode->setColNames(colNames_); + current = projectNode; + } + // Project select properties then dedup + if (dedup_) { + auto *dedupNode = Dedup::make(plan, current); + dedupNode->setInputVar(current->varName()); + dedupNode->setColNames(colNames_); + current = dedupNode; + + // the framework will add data collect to collect the result + // if the result is required + } + root_ = current; + tail_ = getVerticesNode; + return Status::OK(); +} + +Status FetchVerticesValidator::check() { + auto *sentence = static_cast<FetchVerticesSentence*>(sentence_); + spaceId_ = vctx_->whichSpace().id; + + tagName_ = *sentence->tag(); + if (!sentence->isAllTagProps()) { + tagName_ = *(sentence->tag()); + auto tagStatus = qctx_->schemaMng()->toTagID(spaceId_, tagName_); + NG_RETURN_IF_ERROR(tagStatus); + + tagId_ = tagStatus.value(); + schema_ = qctx_->schemaMng()->getTagSchema(spaceId_, tagId_.value()); + if (schema_ == nullptr) { + LOG(ERROR) << "No schema found for " << tagName_; + return Status::Error("No schema found for `%s'", tagName_.c_str()); + } + } + return Status::OK(); +} + +Status FetchVerticesValidator::prepareVertices() { + auto *sentence = static_cast<FetchVerticesSentence*>(sentence_); + // from ref, eval when execute + if (sentence->isRef()) { + src_ = sentence->ref(); + auto result = checkRef(src_, Value::Type::STRING); + NG_RETURN_IF_ERROR(result); + inputVar_ = std::move(result).value(); + return Status::OK(); + } + + // from constant, eval now + // TODO(shylock) add eval() method for expression + QueryExpressionContext dummy(nullptr); + auto vids = sentence->vidList(); + vertices_.reserve(vids.size()); + for (const auto vid : vids) { + // TODO(shylock) Add a new value type VID to semantic this + DCHECK(ExpressionUtils::isConstExpr(vid)); + auto v = vid->eval(dummy); + if (!v.isStr()) { // string as vid + return Status::NotSupported("Not a vertex id"); + } + vertices_.emplace_back(nebula::Row({std::move(v).getStr()})); + } + return Status::OK(); +} + +// TODO(shylock) select _vid property instead of return always. +Status FetchVerticesValidator::prepareProperties() { + auto *sentence = static_cast<FetchVerticesSentence*>(sentence_); + auto *yield = sentence->yieldClause(); + if (yield == nullptr) { + // empty for all tag and properties + props_.clear(); + if (!sentence->isAllTagProps()) { + // for one tag all properties + storage::cpp2::VertexProp prop; + prop.set_tag(tagId_.value()); + // empty for all + props_.emplace_back(std::move(prop)); + outputs_.emplace_back(kVid, Value::Type::STRING); + colNames_.emplace_back(kVid); + for (std::size_t i = 0; i < schema_->getNumFields(); ++i) { + outputs_.emplace_back(schema_->getFieldName(i), + SchemaUtil::propTypeToValueType(schema_->getFieldType(i))); + colNames_.emplace_back(schema_->getFieldName(i)); + } + } else { + // all schema properties + const auto allTagsResult = qctx_->schemaMng()->getAllVerTagSchema(spaceId_); + NG_RETURN_IF_ERROR(allTagsResult); + const auto allTags = std::move(allTagsResult).value(); + std::vector<std::pair<TagID, std::shared_ptr<const meta::NebulaSchemaProvider>>> + allTagsSchema; + allTagsSchema.reserve(allTags.size()); + for (const auto &tag : allTags) { + allTagsSchema.emplace_back(tag.first, tag.second.back()); + } + std::sort(allTagsSchema.begin(), allTagsSchema.end(), [](const auto &a, const auto &b) { + return a.first < b.first; + }); + outputs_.emplace_back(kVid, Value::Type::STRING); + colNames_.emplace_back(kVid); + for (const auto &tagSchema : allTagsSchema) { + for (std::size_t i = 0; i < tagSchema.second->getNumFields(); ++i) { + outputs_.emplace_back( + tagSchema.second->getFieldName(i), + SchemaUtil::propTypeToValueType(tagSchema.second->getFieldType(i))); + colNames_.emplace_back(tagSchema.second->getFieldName(i)); + } + } + } + } else { + CHECK(!sentence->isAllTagProps()) << "Not supported yield for *."; + withProject_ = true; + dedup_ = yield->isDistinct(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId_.value()); + std::vector<std::string> propsName; + propsName.reserve(yield->columns().size()); + for (auto col : yield->columns()) { + if (col->expr()->kind() == Expression::Kind::kSymProperty) { + auto symbolExpr = static_cast<SymbolPropertyExpression *>(col->expr()); + col->setExpr(ExpressionUtils::transSymbolPropertyExpression<TagPropertyExpression>( + symbolExpr)); + } else { + ExpressionUtils::transAllSymbolPropertyExpr<TagPropertyExpression>(col->expr()); + } + const auto *invalidExpr = findInvalidYieldExpression(col->expr()); + if (invalidExpr != nullptr) { + return Status::Error("Invalid yield expression `%s'.", + col->expr()->toString().c_str()); + } + // The properties from storage directly push down only + // The other will be computed in Project Executor + const auto storageExprs = ExpressionUtils::findAllStorage(col->expr()); + for (const auto &storageExpr : storageExprs) { + const auto *expr = static_cast<const SymbolPropertyExpression *>(storageExpr); + if (*expr->sym() != tagName_) { + return Status::Error("Mismatched tag name"); + } + // Check is prop name in schema + if (schema_->getFieldIndex(*expr->prop()) < 0) { + LOG(ERROR) << "Unknown column `" << *expr->prop() << "' in tag `" + << tagName_ << "'."; + return Status::Error("Unknown column `%s' in tag `%s'.", + expr->prop()->c_str(), + tagName_.c_str()); + } + propsName.emplace_back(*expr->prop()); + } + // TODO(shylock) think about the push-down expr + } + prop.set_props(std::move(propsName)); + props_.emplace_back(std::move(prop)); + + // outputs + colNames_ = deduceColNames(yield->yields()); + outputs_.reserve(colNames_.size()); + for (std::size_t i = 0; i < colNames_.size(); ++i) { + auto typeResult = deduceExprType(yield->columns()[i]->expr()); + NG_RETURN_IF_ERROR(typeResult); + outputs_.emplace_back(colNames_[i], typeResult.value()); + } + } + + return Status::OK(); +} + +/*static*/ +const Expression *FetchVerticesValidator::findInvalidYieldExpression(const Expression *root) { + return ExpressionUtils::findAnyKind(root, + {Expression::Kind::kInputProperty, + Expression::Kind::kVarProperty, + Expression::Kind::kEdgeSrc, + Expression::Kind::kEdgeType, + Expression::Kind::kEdgeRank, + Expression::Kind::kEdgeDst}); +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/FetchVerticesValidator.h b/src/validator/FetchVerticesValidator.h new file mode 100644 index 0000000000000000000000000000000000000000..e5860102e0a2a6684b5f39049d7c0f347c8b1af5 --- /dev/null +++ b/src/validator/FetchVerticesValidator.h @@ -0,0 +1,62 @@ +/* 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 _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_ +#define _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_ + +#include "common/base/Base.h" +#include "common/interface/gen-cpp2/storage_types.h" +#include "parser/TraverseSentences.h" +#include "validator/Validator.h" + +namespace nebula { +namespace graph { + +class FetchVerticesValidator final : public Validator { +public: + FetchVerticesValidator(Sentence* sentence, QueryContext* context) + : Validator(sentence, context) {} + +private: + Status validateImpl() override; + + Status toPlan() override; + + Status check(); + + Status prepareVertices(); + + Status prepareProperties(); + + static const Expression* findInvalidYieldExpression(const Expression* root); + +private: + GraphSpaceID spaceId_{0}; + std::vector<nebula::Row> vertices_; + Expression* src_{nullptr}; + std::string tagName_; + // none if not specified tag + folly::Optional<TagID> tagId_; + std::shared_ptr<const meta::SchemaProviderIf> schema_; + std::vector<storage::cpp2::VertexProp> props_; + std::vector<storage::cpp2::Expr> exprs_; + bool dedup_{false}; + std::vector<storage::cpp2::OrderBy> orderBy_{}; + int64_t limit_{std::numeric_limits<int64_t>::max()}; + std::string filter_{}; + // valid when yield expression not require storage + // So expression like these will be evaluate in Project Executor + bool withProject_{false}; + // outputs + std::vector<std::string> colNames_; + // input + std::string inputVar_; +}; + +} // namespace graph +} // namespace nebula + +#endif // _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_ diff --git a/src/validator/GoValidator.cpp b/src/validator/GoValidator.cpp index fb4f9ae7ef9acffb8da89d12284dc3bc730c90d0..aee047db0b7523520e50117ae93e6bd8216db743 100644 --- a/src/validator/GoValidator.cpp +++ b/src/validator/GoValidator.cpp @@ -6,6 +6,8 @@ #include "validator/GoValidator.h" +#include "util/ExpressionUtils.h" + #include "common/base/Base.h" #include "common/expression/VariableExpression.h" #include "common/interface/gen-cpp2/storage_types.h" @@ -123,13 +125,21 @@ Status GoValidator::validateOver(const OverClause* over) { return Status::OK(); } -Status GoValidator::validateWhere(const WhereClause* where) { +Status GoValidator::validateWhere(WhereClause* where) { if (where == nullptr) { return Status::OK(); } filter_ = where->filter(); + if (filter_->kind() == Expression::Kind::kSymProperty) { + auto symbolExpr = static_cast<SymbolPropertyExpression*>(filter_); + where->setFilter(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>( + symbolExpr)); + } else { + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(filter_); + } + auto typeStatus = deduceExprType(filter_); if (!typeStatus.ok()) { return typeStatus.status(); @@ -150,7 +160,7 @@ Status GoValidator::validateWhere(const WhereClause* where) { return Status::OK(); } -Status GoValidator::validateYield(const YieldClause* yield) { +Status GoValidator::validateYield(YieldClause* yield) { if (yield == nullptr) { return Status::Error("Yield clause nullptr."); } @@ -158,6 +168,14 @@ Status GoValidator::validateYield(const YieldClause* yield) { distinct_ = yield->isDistinct(); auto cols = yield->columns(); for (auto col : cols) { + if (col->expr()->kind() == Expression::Kind::kSymProperty) { + auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr()); + col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>( + symbolExpr)); + } else { + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr()); + } + if (!col->getAggFunName().empty()) { return Status::Error( "`%s', not support aggregate function in go sentence.", diff --git a/src/validator/GoValidator.h b/src/validator/GoValidator.h index 9006ad6b1c5ef2c7995a77c63f9386798f92602c..3dd44d9aac4c8119bad3c7e5ceb6c09ba0f80d69 100644 --- a/src/validator/GoValidator.h +++ b/src/validator/GoValidator.h @@ -29,9 +29,9 @@ private: Status validateOver(const OverClause* over); - Status validateWhere(const WhereClause* where); + Status validateWhere(WhereClause* where); - Status validateYield(const YieldClause* yield); + Status validateYield(YieldClause* yield); void extractPropExprs(const Expression* expr); diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp index b11f3551ba38b9d0600bfd28cde04210f7e0b799..17b7d22c2ec6d77ca1637eaaa81d484620c20f7d 100644 --- a/src/validator/Validator.cpp +++ b/src/validator/Validator.cpp @@ -9,6 +9,7 @@ #include "parser/Sentence.h" #include "planner/Query.h" #include "util/SchemaUtil.h" +#include "util/ExpressionUtils.h" #include "validator/AdminValidator.h" #include "validator/AssignmentValidator.h" #include "validator/ExplainValidator.h" @@ -19,6 +20,8 @@ #include "validator/MutateValidator.h" #include "validator/OrderByValidator.h" #include "validator/PipeValidator.h" +#include "validator/FetchVerticesValidator.h" +#include "validator/FetchEdgesValidator.h" #include "validator/ReportError.h" #include "validator/SequentialValidator.h" #include "validator/SetValidator.h" @@ -97,6 +100,10 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon return std::make_unique<InsertVerticesValidator>(sentence, context); case Sentence::Kind::kInsertEdges: return std::make_unique<InsertEdgesValidator>(sentence, context); + case Sentence::Kind::kFetchVertices: + return std::make_unique<FetchVerticesValidator>(sentence, context); + case Sentence::Kind::kFetchEdges: + return std::make_unique<FetchEdgesValidator>(sentence, context); case Sentence::Kind::kCreateSnapshot: return std::make_unique<CreateSnapshotValidator>(sentence, context); case Sentence::Kind::kDropSnapshot: @@ -123,6 +130,8 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) { case PlanNode::Kind::kLoop: case PlanNode::Kind::kMultiOutputs: case PlanNode::Kind::kSwitchSpace: + case PlanNode::Kind::kGetEdges: + case PlanNode::Kind::kGetVertices: case PlanNode::Kind::kCreateSpace: case PlanNode::Kind::kCreateTag: case PlanNode::Kind::kCreateEdge: @@ -660,5 +669,36 @@ bool Validator::evaluableExpr(const Expression* expr) const { return false; } -} // namespace graph -} // namespace nebula +StatusOr<std::string> Validator::checkRef(const Expression *ref, Value::Type type) const { + if (ref->kind() == Expression::Kind::kInputProperty) { + const auto* symExpr = static_cast<const SymbolPropertyExpression*>(ref); + ColDef col(*symExpr->prop(), type); + const auto find = std::find(inputs_.begin(), inputs_.end(), col); + if (find == inputs_.end()) { + return Status::Error("No input property %s", symExpr->prop()->c_str()); + } + return std::string(); + } else if (ref->kind() == Expression::Kind::kVarProperty) { + const auto* symExpr = static_cast<const SymbolPropertyExpression*>(ref); + ColDef col(*symExpr->prop(), type); + const auto &varName = *symExpr->sym(); + const auto &var = vctx_->getVar(varName); + if (var.empty()) { + return Status::Error("No variable %s", varName.c_str()); + } + const auto find = std::find(var.begin(), var.end(), col); + if (find == var.end()) { + return Status::Error("No property %s in variable %s", + symExpr->prop()->c_str(), + varName.c_str()); + } + return varName; + } else { + // it's guranteed by parser + DLOG(FATAL) << "Unexpected expression " << ref->kind(); + return Status::Error("Unexpected expression."); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/Validator.h b/src/validator/Validator.h index 0eae4243e7b9556bd11c87f959e05057f475527a..4dd81a0210c674767e36c9cbac9a177134e3b021 100644 --- a/src/validator/Validator.h +++ b/src/validator/Validator.h @@ -88,6 +88,10 @@ protected: static Status appendPlan(PlanNode* plan, PlanNode* appended); + // Check the variable or input property reference + // return the input variable + StatusOr<std::string> checkRef(const Expression *ref, const Value::Type type) const; + protected: SpaceDescription space_; Sentence* sentence_{nullptr}; diff --git a/src/validator/YieldValidator.cpp b/src/validator/YieldValidator.cpp index d68bac18e8fea063f0bfd22b59cc73900cd52057..acb338060dd4ca17e426b2b71001f6af00a0e224 100644 --- a/src/validator/YieldValidator.cpp +++ b/src/validator/YieldValidator.cpp @@ -5,7 +5,7 @@ */ #include "validator/YieldValidator.h" - +#include "util/ExpressionUtils.h" #include "common/expression/Expression.h" #include "context/QueryContext.h" #include "parser/Clauses.h" @@ -20,6 +20,7 @@ YieldValidator::YieldValidator(Sentence *sentence, QueryContext *qctx) Status YieldValidator::validateImpl() { auto yield = static_cast<YieldSentence *>(sentence_); + rebuildYield(yield->yield()); NG_RETURN_IF_ERROR(validateYieldAndBuildOutputs(yield->yield())); NG_RETURN_IF_ERROR(validateWhere(yield->where())); @@ -261,5 +262,19 @@ Status YieldValidator::toPlan() { return Status::OK(); } +void YieldValidator::rebuildYield(YieldClause* yield) { + // TODO(shylock) keep same with previous so transfer to EdgePropertyExpression + auto columns = yield->columns(); + for (auto& col : columns) { + if (col->expr()->kind() == Expression::Kind::kSymProperty) { + auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr()); + col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>( + symbolExpr)); + } else { + ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr()); + } + } +} + } // namespace graph } // namespace nebula diff --git a/src/validator/YieldValidator.h b/src/validator/YieldValidator.h index 605dee94c56bc7fa980f4c7853d5757b34b3af5a..eadcbdf84bd1b570920f7a932fd2e333eded7551 100644 --- a/src/validator/YieldValidator.h +++ b/src/validator/YieldValidator.h @@ -42,6 +42,7 @@ private: YieldColumns *getYieldColumns(YieldColumns *yieldColumns, ObjectPool *objPool, size_t numColumns); + void rebuildYield(YieldClause* yield); bool hasAggFun_{false}; }; diff --git a/src/validator/test/CMakeLists.txt b/src/validator/test/CMakeLists.txt index f007458e8babff6bc3c1930c2762a803df8709ba..c05ef4ea7fe0ceb6b107e6b6017424b97ab96f2d 100644 --- a/src/validator/test/CMakeLists.txt +++ b/src/validator/test/CMakeLists.txt @@ -49,6 +49,8 @@ set(VALIDATOR_TEST_LIBS nebula_add_test( NAME validator_test SOURCES + FetchVerticesTest.cpp + FetchEdgesTest.cpp MockSchemaManagerTest.cpp ValidatorTestBase.cpp QueryValidatorTest.cpp diff --git a/src/validator/test/FetchEdgesTest.cpp b/src/validator/test/FetchEdgesTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f573acab3aee3d06e324df2fbffe0f11aab1ec7e --- /dev/null +++ b/src/validator/test/FetchEdgesTest.cpp @@ -0,0 +1,509 @@ +/* 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 "planner/Query.h" +#include "validator/FetchEdgesValidator.h" +#include "validator/test/ValidatorTestBase.h" + +namespace nebula { +namespace graph { + +class FetchEdgesValidatorTest : public ValidatorTestBase {}; + +TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) { + { + ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\"")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto edgeTypeResult = schemaMng_->toEdgeType(1, "like"); + ASSERT_TRUE(edgeTypeResult.ok()); + auto edgeType = edgeTypeResult.value(); + std::vector<nebula::Row> edges{nebula::Row({ + "1", + edgeType, + 0, + "2", + })}; + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType); + prop.set_props({kSrc, kDst, kRank, "start", "end", "likeness"}); + std::vector<storage::cpp2::EdgeProp> props; + props.emplace_back(std::move(prop)); + auto *ge = GetEdges::make(expectedQueryCtx_->plan(), + start, + 1, + std::move(edges), + nullptr, + edgeType, + nullptr, + nullptr, + std::move(props), + {}); + expectedQueryCtx_->plan()->setRoot(ge); + auto result = Eq(plan->root(), ge); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD + { + ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, like.end")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto edgeTypeResult = schemaMng_->toEdgeType(1, "like"); + ASSERT_TRUE(edgeTypeResult.ok()); + auto edgeType = edgeTypeResult.value(); + std::vector<nebula::Row> edges{nebula::Row({ + "1", + edgeType, + 0, + "2", + })}; + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType); + prop.set_props({kSrc, kDst, kRank, "start", "end"}); + std::vector<storage::cpp2::EdgeProp> props; + props.emplace_back(std::move(prop)); + std::vector<storage::cpp2::Expr> exprs; + storage::cpp2::Expr expr1; + expr1.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("start")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("end")).encode()); + exprs.emplace_back(std::move(expr1)); + exprs.emplace_back(std::move(expr2)); + auto *ge = GetEdges::make(expectedQueryCtx_->plan(), + start, + 1, + std::move(edges), + nullptr, + edgeType, + nullptr, + nullptr, + std::move(props), + std::move(exprs)); + + // Project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("start")))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("end")))); + auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get()); + project->setColNames({std::string("like.") + kSrc, + std::string("like.") + kDst, + std::string("like.") + kRank, + "like.start", + "like.end"}); + expectedQueryCtx_->plan()->setRoot(project); + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD const expression + { + ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, 1 + 1, like.end")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + + // GetEdges + auto edgeTypeResult = schemaMng_->toEdgeType(1, "like"); + ASSERT_TRUE(edgeTypeResult.ok()); + auto edgeType = edgeTypeResult.value(); + std::vector<nebula::Row> edges{nebula::Row({ + "1", + edgeType, + 0, + "2", + })}; + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType); + prop.set_props({kSrc, kDst, kRank, "start", "end"}); + std::vector<storage::cpp2::EdgeProp> props; + props.emplace_back(std::move(prop)); + std::vector<storage::cpp2::Expr> exprs; + storage::cpp2::Expr expr1; + expr1.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("start")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("end")).encode()); + exprs.emplace_back(std::move(expr1)); + exprs.emplace_back(std::move(expr2)); + auto *ge = GetEdges::make(expectedQueryCtx_->plan(), + start, + 1, + std::move(edges), + nullptr, + edgeType, + nullptr, + nullptr, + std::move(props), + std::move(exprs)); + + // Project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("start")))); + yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression( + Expression::Kind::kAdd, new ConstantExpression(1), new ConstantExpression(1)))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("end")))); + auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get()); + project->setColNames({std::string("like.") + kSrc, + std::string("like.") + kDst, + std::string("like.") + kRank, + "like.start", + "(1+1)", + "like.end"}); + expectedQueryCtx_->plan()->setRoot(project); + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD combine properties + { + ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start > like.end")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto edgeTypeResult = schemaMng_->toEdgeType(1, "like"); + ASSERT_TRUE(edgeTypeResult.ok()); + auto edgeType = edgeTypeResult.value(); + std::vector<nebula::Row> edges{nebula::Row({ + "1", + edgeType, + 0, + "2", + })}; + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType); + std::vector<std::string> propsName; + prop.set_props({kSrc, kDst, kRank, "start", "end"}); + std::vector<storage::cpp2::EdgeProp> props; + props.emplace_back(std::move(prop)); + std::vector<storage::cpp2::Expr> exprs; + storage::cpp2::Expr expr1; + expr1.set_expr( + RelationalExpression( + Expression::Kind::kRelGT, + new EdgePropertyExpression(new std::string("like"), new std::string("start")), + new EdgePropertyExpression(new std::string("like"), new std::string("end"))) + .encode()); + exprs.emplace_back(std::move(expr1)); + auto *ge = GetEdges::make(expectedQueryCtx_->plan(), + start, + 1, + std::move(edges), + nullptr, + edgeType, + nullptr, + nullptr, + std::move(props), + std::move(exprs)); + + // project, TODO(shylock) it's could push-down to storage if it supported + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new RelationalExpression( + Expression::Kind::kRelGT, + new EdgePropertyExpression(new std::string("like"), new std::string("start")), + new EdgePropertyExpression(new std::string("like"), new std::string("end"))))); + auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get()); + project->setColNames({std::string("like.") + kSrc, + std::string("like.") + kDst, + std::string("like.") + kRank, + "(like.start>like.end)"}); + + expectedQueryCtx_->plan()->setRoot(project); + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD distinct + { + ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD distinct like.start, like.end")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto edgeTypeResult = schemaMng_->toEdgeType(1, "like"); + ASSERT_TRUE(edgeTypeResult.ok()); + auto edgeType = edgeTypeResult.value(); + std::vector<nebula::Row> edges{nebula::Row({ + "1", + edgeType, + 0, + "2", + })}; + storage::cpp2::EdgeProp prop; + prop.set_type(edgeType); + prop.set_props({kSrc, kDst, kRank, "start", "end"}); + std::vector<storage::cpp2::EdgeProp> props; + props.emplace_back(std::move(prop)); + std::vector<storage::cpp2::Expr> exprs; + storage::cpp2::Expr expr1; + expr1.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("start")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + EdgePropertyExpression(new std::string("like"), new std::string("end")).encode()); + exprs.emplace_back(std::move(expr1)); + exprs.emplace_back(std::move(expr2)); + auto *ge = GetEdges::make(expectedQueryCtx_->plan(), + start, + 1, + std::move(edges), + nullptr, + edgeType, + nullptr, + nullptr, + std::move(props), + std::move(exprs)); + + std::vector<std::string> colNames{std::string("like.") + kSrc, + std::string("like.") + kDst, + std::string("like.") + kRank, + "like.start", + "like.end"}; + + // project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like")))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("start")))); + yieldColumns->addColumn(new YieldColumn( + new EdgePropertyExpression(new std::string("like"), new std::string("end")))); + auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get()); + project->setColNames({std::string("like.") + kSrc, + std::string("like.") + kDst, + std::string("like.") + kRank, + "like.start", + "like.end"}); + // dedup + auto *dedup = Dedup::make(expectedQueryCtx_->plan(), project); + dedup->setColNames(colNames); + + // data collect + auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(), + dedup, + DataCollect::CollectKind::kRowBasedMove, + {dedup->varName()}); + dataCollect->setColNames(colNames); + + expectedQueryCtx_->plan()->setRoot(dataCollect); + auto result = Eq(plan->root(), dataCollect); + ASSERT_TRUE(result.ok()) << result; + } +} + +TEST_F(FetchEdgesValidatorTest, FetchEdgesInputOutput) { + // var + { + const std::string query = "$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;" + "FETCH PROP ON like $a.src->$a.dst@$a.rank"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kStart, + })); + } + // pipe + { + const std::string query = "FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank" + " | FETCH PROP ON like $-.src->$-.dst@$-.rank"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kStart, + })); + } + + // with project + // var + { + const std::string query = + "$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank + 1 AS rank;" + "FETCH PROP ON like $a.src->$a.dst@$a.rank " + "YIELD like._src + 1"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kStart, + })); + } + // pipe + { + const std::string query = "FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank + 1 AS rank" + " | FETCH PROP ON like $-.src->$-.dst@$-.rank " + "YIELD like._src + 1"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetEdges, + PlanNode::Kind::kStart, + })); + } +} + +TEST_F(FetchEdgesValidatorTest, FetchEdgesPropFailed) { + // mismatched tag + { + auto result = GQLParser().parse("FETCH PROP ON edge1 \"1\"->\"2\" YIELD edge2.prop2"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // notexist edge + { + auto result = GQLParser().parse("FETCH PROP ON not_exist_edge \"1\"->\"2\" " + "YIELD not_exist_edge.prop1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // notexist edge property + { + auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" " + "YIELD like.not_exist_prop"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // invalid yield expression + { + auto result = GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src;" + "FETCH PROP ON like \"1\"->\"2\" YIELD $a.src + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src | " + "FETCH PROP ON like \"1\"->\"2\" YIELD $-.src + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" " + "YIELD $^.like.start + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" " + "YIELD $$.like.start + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } +} + +TEST_F(FetchEdgesValidatorTest, FetchEdgesInputFailed) { + // mismatched variable + { + auto result = + GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;" + "FETCH PROP ON like $b.src->$b.dst@$b.rank"); + ASSERT_TRUE(result.ok()) << result.status(); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // mismatched variable property + { + auto result = + GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;" + "FETCH PROP ON like $b.src->$b.dst@$b.not_exist_property"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // mismatched input property + { + auto result = + GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank | " + "FETCH PROP ON like $-.src->$-.dst@$-.not_exist_property"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // refer to different variables + { + auto result = + GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;" + "$b = FETCH PROP ON like \"1\"->\"2\" " + "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;" + "FETCH PROP ON like $a.src->$b.dst@$b.rank"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/test/FetchVerticesTest.cpp b/src/validator/test/FetchVerticesTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83042c6db6698f8ce1fb05aef66d8b37db696a4e --- /dev/null +++ b/src/validator/test/FetchVerticesTest.cpp @@ -0,0 +1,409 @@ +/* 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 "planner/Query.h" +#include "validator/FetchVerticesValidator.h" +#include "validator/test/ValidatorTestBase.h" + +namespace nebula { +namespace graph { + +class FetchVerticesValidatorTest : public ValidatorTestBase {}; + +TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { + { + ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\"")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId); + auto *gv = GetVertices::make(expectedQueryCtx_->plan(), + start, + 1, + std::vector<Row>{Row({"1"})}, + nullptr, + std::vector<storage::cpp2::VertexProp>{std::move(prop)}, + {}); + auto result = Eq(plan->root(), gv); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD + { + ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name, person.age")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId); + prop.set_props(std::vector<std::string>{"name", "age"}); + storage::cpp2::Expr expr1; + expr1.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("name")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("age")).encode()); + auto *gv = + GetVertices::make(expectedQueryCtx_->plan(), + start, + 1, + std::vector<Row>{Row({"1"})}, + nullptr, + std::vector<storage::cpp2::VertexProp>{std::move(prop)}, + std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)}); + + // project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("name")))); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("age")))); + auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get()); + project->setColNames({"person.name", "person.age"}); + + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD const expression + { + ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name, 1 > 1, person.age")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + + // get vertices + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId); + prop.set_props(std::vector<std::string>{"name", "age"}); + storage::cpp2::Expr expr1; + expr1.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("name")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("age")).encode()); + auto *gv = + GetVertices::make(expectedQueryCtx_->plan(), + start, + 1, + std::vector<Row>{Row({"1"})}, + nullptr, + std::vector<storage::cpp2::VertexProp>{std::move(prop)}, + std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)}); + + // project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("name")))); + yieldColumns->addColumn(new YieldColumn(new RelationalExpression( + Expression::Kind::kRelGT, new ConstantExpression(1), new ConstantExpression(1)))); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("age")))); + auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get()); + project->setColNames({"person.name", "(1>1)", "person.age"}); + + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD combine properties + { + ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name + person.age")); + auto *plan = qCtx_->plan(); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId); + prop.set_props(std::vector<std::string>{"name", "age"}); + storage::cpp2::Expr expr1; + expr1.set_expr( + ArithmeticExpression( + Expression::Kind::kAdd, + new TagPropertyExpression(new std::string("person"), new std::string("name")), + new TagPropertyExpression(new std::string("person"), new std::string("age"))) + .encode()); + + auto *gv = GetVertices::make(expectedQueryCtx_->plan(), + start, + 1, + std::vector<Row>{Row({"1"})}, + nullptr, + std::vector<storage::cpp2::VertexProp>{std::move(prop)}, + std::vector<storage::cpp2::Expr>{std::move(expr1)}); + + // project, TODO(shylock) could push down to storage is it supported + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression( + Expression::Kind::kAdd, + new TagPropertyExpression(new std::string("person"), new std::string("name")), + new TagPropertyExpression(new std::string("person"), new std::string("age"))))); + auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get()); + project->setColNames({"(person.name+person.age)"}); + + auto result = Eq(plan->root(), project); + ASSERT_TRUE(result.ok()) << result; + } + // With YIELD distinct + { + ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD distinct person.name, person.age")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp prop; + prop.set_tag(tagId); + prop.set_props(std::vector<std::string>{"name", "age"}); + storage::cpp2::Expr expr1; + expr1.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("name")).encode()); + storage::cpp2::Expr expr2; + expr2.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("age")).encode()); + auto *gv = + GetVertices::make(expectedQueryCtx_->plan(), + start, + 1, + std::vector<Row>{Row({"1"})}, + nullptr, + std::vector<storage::cpp2::VertexProp>{std::move(prop)}, + std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)}); + + std::vector<std::string> colNames{"person.name", "person.age"}; + + // project + auto yieldColumns = std::make_unique<YieldColumns>(); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("name")))); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("age")))); + auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get()); + project->setColNames({"person.name", "person.age"}); + + // dedup + auto *dedup = Dedup::make(expectedQueryCtx_->plan(), project); + dedup->setColNames(colNames); + + // data collect + auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(), + dedup, + DataCollect::CollectKind::kRowBasedMove, + {dedup->varName()}); + dataCollect->setColNames(colNames); + + auto result = Eq(plan->root(), dataCollect); + ASSERT_TRUE(result.ok()) << result; + } + // ON * + { + ASSERT_TRUE(toPlan("FETCH PROP ON * \"1\"")); + + auto *start = StartNode::make(expectedQueryCtx_->plan()); + + auto *plan = qCtx_->plan(); + + auto *gv = GetVertices::make( + expectedQueryCtx_->plan(), start, 1, std::vector<Row>{Row({"1"})}, nullptr, {}, {}); + auto result = Eq(plan->root(), gv); + ASSERT_TRUE(result.ok()) << result; + } +} + +TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { + // pipe + { + const std::string query = "FETCH PROP ON person \"1\" YIELD person.name AS name" + " | FETCH PROP ON person $-.name"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } + // Variable + { + const std::string query = "$a = FETCH PROP ON person \"1\" YIELD person.name AS name;" + "FETCH PROP ON person $a.name"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } + + // with project + // pipe + { + const std::string query = "FETCH PROP ON person \"1\" YIELD person.name + 1 AS name" + " | FETCH PROP ON person $-.name YIELD person.name + 1"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } + // Variable + { + const std::string query = "$a = FETCH PROP ON person \"1\" YIELD person.name + 1 AS name;" + "FETCH PROP ON person $a.name YIELD person.name + 1 "; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } +} + +TEST_F(FetchVerticesValidatorTest, FetchVerticesPropFailed) { + // mismatched tag + { + auto result = GQLParser().parse("FETCH PROP ON tag1 \"1\" YIELD tag2.prop2"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // not exist tag + { + auto result = + GQLParser().parse("FETCH PROP ON not_exist_tag \"1\" YIELD not_exist_tag.prop1"); + ASSERT_TRUE(result.ok()) << result.status(); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // not exist property + { + auto result = + GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.not_exist_property"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // invalid yield expression + { + auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;" + " FETCH PROP ON person \"1\" YIELD $a.name + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + // invalid yield expression + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.name AS name | " + " FETCH PROP ON person \"1\" YIELD $-.name + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._src + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._type"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._rank + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._dst + 1"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } +} + +TEST_F(FetchVerticesValidatorTest, FetchVerticesInputFailed) { + // mismatched varirable + { + auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;" + "FETCH PROP ON person $b.name"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // mismatched varirable property + { + auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;" + "FETCH PROP ON person $a.not_exist_property"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } + + // mismatched input property + { + auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.name AS name | " + "FETCH PROP ON person $-.not_exist_property"); + ASSERT_TRUE(result.ok()); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + auto validateResult = validator.validate(); + ASSERT_FALSE(validateResult.ok()); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/test/MockSchemaManager.cpp b/src/validator/test/MockSchemaManager.cpp index 25f233cc5184c5e612c92fdbc637d97e2eb69aad..511bf782259b1dd0b2c7595051a3cd1d89e30c65 100644 --- a/src/validator/test/MockSchemaManager.cpp +++ b/src/validator/test/MockSchemaManager.cpp @@ -9,6 +9,11 @@ namespace nebula { namespace graph { +// space: test_space +// tag: +// person(name string, age int8) +// edge: +// like(start timestamp, end timestamp, likeness int64) void MockSchemaManager::init() { spaceNameIds_.emplace("test_space", 1); tagNameIds_.emplace("person", 2); diff --git a/src/validator/test/MockSchemaManager.h b/src/validator/test/MockSchemaManager.h index 7ef370f136edecb7d7794c3da3f5083ba46e7951..b9f914432da26bef20fb679deecb487573b4ce7a 100644 --- a/src/validator/test/MockSchemaManager.h +++ b/src/validator/test/MockSchemaManager.h @@ -19,6 +19,12 @@ public: MockSchemaManager() = default; ~MockSchemaManager() = default; + static std::unique_ptr<MockSchemaManager> makeUnique() { + auto instance = std::make_unique<MockSchemaManager>(); + instance->init(); + return instance; + } + void init(); std::shared_ptr<const nebula::meta::NebulaSchemaProvider> diff --git a/src/validator/test/ValidatorTestBase.cpp b/src/validator/test/ValidatorTestBase.cpp index 80acd68cd123f26e8ef0489234f762b83b1d39a2..606fed112cc7a199e9154ffc2692dbd254002ab8 100644 --- a/src/validator/test/ValidatorTestBase.cpp +++ b/src/validator/test/ValidatorTestBase.cpp @@ -4,11 +4,244 @@ * attached with Common Clause Condition 1.0, found in the LICENSES directory. */ +#include "common/base/Base.h" +#include "context/QueryContext.h" +#include "context/ValidateContext.h" +#include "parser/GQLParser.h" +#include "planner/Admin.h" +#include "planner/ExecutionPlan.h" +#include "planner/Maintain.h" +#include "planner/Mutate.h" +#include "planner/PlanNode.h" +#include "planner/Query.h" +#include "validator/ASTValidator.h" #include "validator/test/ValidatorTestBase.h" namespace nebula { namespace graph { +// Compare the node itself's field in this function +/*static*/ Status ValidatorTestBase::EqSelf(const PlanNode *l, const PlanNode *r) { + if (l != nullptr && r != nullptr) { + // continue + } else if (l == nullptr && r == nullptr) { + return Status::OK(); + } else { + return Status::Error("%s.this != %s.this", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + if (l->kind() != r->kind()) { + return Status::Error( + "%s.kind_ != %s.kind_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + if (l->colNamesRef() != r->colNamesRef()) { + return Status::Error( + "%s.colNames_ != %s.colNames_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + switch (l->kind()) { + case PlanNode::Kind::kUnknown: + return Status::Error("Unknown node"); + case PlanNode::Kind::kStart: + case PlanNode::Kind::kDedup: + return Status::OK(); + case PlanNode::Kind::kDeleteEdges: + case PlanNode::Kind::kDeleteVertices: + case PlanNode::Kind::kIndexScan: + case PlanNode::Kind::kGetNeighbors: + case PlanNode::Kind::kFilter: + case PlanNode::Kind::kSort: + case PlanNode::Kind::kLimit: + case PlanNode::Kind::kAggregate: + case PlanNode::Kind::kSwitchSpace: + case PlanNode::Kind::kMultiOutputs: + case PlanNode::Kind::kCreateSpace: + case PlanNode::Kind::kCreateTag: + case PlanNode::Kind::kCreateEdge: + case PlanNode::Kind::kDescSpace: + case PlanNode::Kind::kDescTag: + case PlanNode::Kind::kDescEdge: + case PlanNode::Kind::kInsertVertices: + case PlanNode::Kind::kInsertEdges: + case PlanNode::Kind::kUnion: + case PlanNode::Kind::kIntersect: + case PlanNode::Kind::kMinus: + case PlanNode::Kind::kSelect: + case PlanNode::Kind::kLoop: + case PlanNode::Kind::kAlterEdge: + case PlanNode::Kind::kAlterTag: + case PlanNode::Kind::kShowCreateSpace: + case PlanNode::Kind::kShowCreateTag: + case PlanNode::Kind::kShowCreateEdge: + case PlanNode::Kind::kDropSpace: + case PlanNode::Kind::kDropTag: + case PlanNode::Kind::kDropEdge: + case PlanNode::Kind::kShowSpaces: + case PlanNode::Kind::kShowTags: + case PlanNode::Kind::kShowEdges: + case PlanNode::Kind::kCreateSnapshot: + case PlanNode::Kind::kDropSnapshot: + case PlanNode::Kind::kShowSnapshots: + case PlanNode::Kind::kDataJoin: + LOG(FATAL) << "Unimplemented"; + case PlanNode::Kind::kDataCollect: { + const auto *lDC = static_cast<const DataCollect*>(l); + const auto *rDC = static_cast<const DataCollect*>(r); + if (lDC->collectKind() != rDC->collectKind()) { + return Status::Error( + "%s.collectKind_ != %s.collectKind_", + l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + return Status::OK(); + } + case PlanNode::Kind::kGetVertices: { + const auto *lGV = static_cast<const GetVertices *>(l); + const auto *rGV = static_cast<const GetVertices *>(r); + // vertices + if (lGV->vertices() != rGV->vertices()) { + return Status::Error( + "%s.vertices_ != %s.vertices_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // src + if (lGV->src() != nullptr && rGV->src() != nullptr) { + if (*lGV->src() != *rGV->src()) { + return Status::Error( + "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + } else if (lGV->src() != rGV->src()) { + return Status::Error( + "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // props + if (lGV->props() != rGV->props()) { + return Status::Error( + "%s.props_ != %s.props_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + return Status::OK(); + } + case PlanNode::Kind::kGetEdges: { + const auto *lGE = static_cast<const GetEdges *>(l); + const auto *rGE = static_cast<const GetEdges *>(r); + // vertices + if (lGE->edges() != rGE->edges()) { + return Status::Error( + "%s.vertices_ != %s.vertices_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // src + if (lGE->src() != nullptr && rGE->src() != nullptr) { + if (*lGE->src() != *rGE->src()) { + return Status::Error( + "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + } else if (lGE->src() != rGE->src()) { + return Status::Error( + "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // dst + if (lGE->dst() != nullptr && rGE->dst() != nullptr) { + if (*lGE->dst() != *rGE->dst()) { + return Status::Error( + "%s.dst_ != %s.dst_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + } else if (lGE->dst() != rGE->dst()) { + return Status::Error( + "%s.dst_ != %s.dst_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // ranking + if (lGE->ranking() != nullptr && rGE->ranking() != nullptr) { + if (*lGE->ranking() != *lGE->ranking()) { + return Status::Error( + "%s.ranking_ != %s.ranking_", + l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + } else if (lGE->ranking() != rGE->ranking()) { + return Status::Error( + "%s.ranking_ != %s.ranking_", + l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + // props + if (lGE->props() != rGE->props()) { + return Status::Error( + "%s.props_ != %s.props_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + return Status::OK(); + } + case PlanNode::Kind::kProject: { + const auto *lp = static_cast<const Project *>(l); + const auto *rp = static_cast<const Project *>(r); + if (lp->columns() == nullptr && rp->columns() == nullptr) { + } else if (lp->columns() != nullptr && rp->columns() != nullptr) { + auto lCols = lp->columns()->columns(); + auto rCols = rp->columns()->columns(); + if (lCols.size() != rCols.size()) { + return Status::Error("%s.columns_ != %s.columns_", + l->nodeLabel().c_str(), + r->nodeLabel().c_str()); + } + for (std::size_t i = 0; i < lCols.size(); ++i) { + if (*lCols[i] != *rCols[i]) { + return Status::Error("%s.columns_ != %s.columns_", + l->nodeLabel().c_str(), + r->nodeLabel().c_str()); + } + } + } else { + return Status::Error( + "%s.columns_ != %s.columns_", l->nodeLabel().c_str(), r->nodeLabel().c_str()); + } + return Status::OK(); + } + } + LOG(FATAL) << "Unknow plan node kind " << static_cast<int>(l->kind()); +} + +// only traversal the plan by inputs, only `select` or `loop` make ring +// So it's a DAG +// There are some node visited multiple times but it doesn't matter +// For Example: +// A +// | | +// V V +// B C +// | | +// V V +// D +// | +// V +// E +// this will traversal sub-tree [D->E] twice but not matter the Equal result +// TODO(shylock) maybe need check the toplogy of `Select` and `Loop` +/*static*/ Status ValidatorTestBase::Eq(const PlanNode *l, const PlanNode *r) { + auto result = EqSelf(l, r); + if (!result.ok()) { + return result; + } + + const auto *lSingle = dynamic_cast<const SingleDependencyNode *>(l); + const auto *rSingle = dynamic_cast<const SingleDependencyNode *>(r); + if (lSingle != nullptr) { + const auto *lDep = lSingle->dep(); + const auto *rDep = CHECK_NOTNULL(rSingle)->dep(); + return Eq(lDep, rDep); + } + + const auto *lBi = dynamic_cast<const BiInputNode *>(l); + const auto *rBi = dynamic_cast<const BiInputNode *>(r); + if (lBi != nullptr) { + const auto *llInput = lBi->left(); + const auto *rlInput = CHECK_NOTNULL(rBi)->left(); + result = Eq(llInput, rlInput); + if (!result.ok()) { + return result; + } + + const auto *lrInput = lBi->right(); + const auto *rrInput = rBi->right(); + result = Eq(lrInput, rrInput); + return result; + } + + return result; +} + std::ostream& operator<<(std::ostream& os, const std::vector<PlanNode::Kind>& plan) { std::vector<const char*> kinds(plan.size()); std::transform(plan.cbegin(), plan.cend(), kinds.begin(), PlanNode::toString); diff --git a/src/validator/test/ValidatorTestBase.h b/src/validator/test/ValidatorTestBase.h index 3e93991d8dd55f8f27fab39328af2d8c635badb3..fa09d1c54f112a8b59e9c32d171520f8fce3ac41 100644 --- a/src/validator/test/ValidatorTestBase.h +++ b/src/validator/test/ValidatorTestBase.h @@ -4,10 +4,10 @@ * attached with Common Clause Condition 1.0, found in the LICENSES directory. */ -#ifndef VALIDATOR_TEST_VALIDATORTESTBASE_H_ -#define VALIDATOR_TEST_VALIDATORTESTBASE_H_ +#ifndef _VALIDATOR_TEST_VALIDATOR_TEST_BASE_H_ +#define _VALIDATOR_TEST_VALIDATOR_TEST_BASE_H_ -#include <vector> +#include "common/base/Base.h" #include <gtest/gtest.h> @@ -30,15 +30,40 @@ protected: void SetUp() override { session_ = Session::create(0); session_->setSpace("test_space", 1); - schemaMng_ = std::make_unique<MockSchemaManager>(); - schemaMng_->init(); + schemaMng_ = CHECK_NOTNULL(MockSchemaManager::makeUnique()); + qCtx_ = buildContext(); + expectedQueryCtx_ = buildContext(); } void TearDown() override { - session_.reset(); - schemaMng_.reset(); } + // some utils + inline ::testing::AssertionResult toPlan(const std::string &query) { + auto result = GQLParser().parse(query); + if (!result.ok()) { + return ::testing::AssertionFailure() << result.status().toString(); + } + sentences_ = std::move(result).value(); + qCtx_ = buildContext(); + ASTValidator validator(sentences_.get(), qCtx_.get()); + auto validateResult = validator.validate(); + if (!validateResult.ok()) { + return ::testing::AssertionFailure() << validateResult.toString(); + } + return ::testing::AssertionSuccess(); + } + + StatusOr<ExecutionPlan*> validate(const std::string& query) { + auto result = GQLParser().parse(query); + if (!result.ok()) return std::move(result).status(); + auto sentences = std::move(result).value(); + ASTValidator validator(sentences.get(), qCtx_.get()); + NG_RETURN_IF_ERROR(validator.validate()); + return qCtx_->plan(); + } + + std::unique_ptr<QueryContext> buildContext() { auto rctx = std::make_unique<RequestContext<cpp2::ExecutionResponse>>(); rctx->setSession(session_); @@ -49,6 +74,10 @@ protected: return qctx; } + static Status EqSelf(const PlanNode* l, const PlanNode* r); + + static Status Eq(const PlanNode *l, const PlanNode *r); + ::testing::AssertionResult checkResult(const std::string& query, const std::vector<PlanNode::Kind>& expected = {}, const std::vector<std::string> &rootColumns = {}) { @@ -196,6 +225,10 @@ protected: protected: std::shared_ptr<Session> session_; std::unique_ptr<MockSchemaManager> schemaMng_; + std::unique_ptr<QueryContext> qCtx_; + std::unique_ptr<Sentence> sentences_; + // used to hold the expected query plan + std::unique_ptr<QueryContext> expectedQueryCtx_; }; std::ostream& operator<<(std::ostream& os, const std::vector<PlanNode::Kind>& plan);