diff --git a/src/graph/context/ast/CypherAstContext.h b/src/graph/context/ast/CypherAstContext.h index 47b9a184bf2..1b2b68539ce 100644 --- a/src/graph/context/ast/CypherAstContext.h +++ b/src/graph/context/ast/CypherAstContext.h @@ -230,40 +230,38 @@ struct CypherContext final : AstContext { }; struct PatternContext { - explicit PatternContext(PatternKind k) : kind(k) {} + PatternContext(PatternKind k, QueryContext* q, WhereClauseContext* b, GraphSpaceID g) + : kind(k), qctx(q), bindWhereClause(b), spaceId(g) {} + const PatternKind kind; + + QueryContext* qctx{nullptr}; + WhereClauseContext* bindWhereClause{nullptr}; + GraphSpaceID spaceId; + + // Output fields + ScanInfo scanInfo; + // initialize start expression in project node + Expression* initialExpr{nullptr}; }; struct NodeContext final : PatternContext { NodeContext(QueryContext* q, WhereClauseContext* b, GraphSpaceID g, const NodeInfo* i) - : PatternContext(PatternKind::kNode), qctx(q), bindWhereClause(b), spaceId(g), info(i) {} + : PatternContext(PatternKind::kNode, q, b, g), info(i) {} - QueryContext* qctx; - WhereClauseContext* bindWhereClause; - GraphSpaceID spaceId; const NodeInfo* info; std::unordered_set* nodeAliasesAvailable{nullptr}; // Output fields - ScanInfo scanInfo; Set ids; - // initialize start expression in project node - Expression* initialExpr{nullptr}; + std::string refVarName; }; struct EdgeContext final : PatternContext { EdgeContext(QueryContext* q, WhereClauseContext* b, GraphSpaceID g, const EdgeInfo* i) - : PatternContext(PatternKind::kEdge), qctx(q), bindWhereClause(b), spaceId(g), info(i) {} + : PatternContext(PatternKind::kEdge, q, b, g), info(i) {} - QueryContext* qctx; - WhereClauseContext* bindWhereClause; - GraphSpaceID spaceId; const EdgeInfo* info; - - // Output fields - ScanInfo scanInfo; - // initialize start expression in project node - Expression* initialExpr{nullptr}; }; } // namespace graph diff --git a/src/graph/executor/logic/ArgumentExecutor.cpp b/src/graph/executor/logic/ArgumentExecutor.cpp index 36d1eeff39f..de84992fcf4 100644 --- a/src/graph/executor/logic/ArgumentExecutor.cpp +++ b/src/graph/executor/logic/ArgumentExecutor.cpp @@ -16,32 +16,46 @@ folly::Future ArgumentExecutor::execute() { // MemoryTrackerVerified auto *argNode = asNode(node()); auto &alias = argNode->getAlias(); - auto iter = ectx_->getResult(argNode->inputVar()).iter(); - DCHECK(iter != nullptr); + auto iter = DCHECK_NOTNULL(ectx_->getResult(argNode->inputVar()).iter()); - const auto &successor = successors(); - auto kind = (*successor.begin())->node()->kind(); - bool flag = (kind != PlanNode::Kind::kGetVertices && kind != PlanNode::Kind::kExpand); + auto sz = iter->size(); DataSet ds; ds.colNames = argNode->colNames(); - ds.rows.reserve(iter->size()); + ds.rows.reserve(sz); + VidHashSet unique; + unique.reserve(sz); + + auto addRow = [&unique, &ds](const Value &v) { + if (unique.emplace(v).second) { + Row row; + row.values.emplace_back(v); + ds.rows.emplace_back(std::move(row)); + } + }; + for (; iter->valid(); iter->next()) { auto &val = iter->getColumn(alias); if (val.isNull()) { continue; } - // TODO(jmq) analyze the type of val in the validation phase - if (flag && !val.isVertex()) { - return Status::Error("Argument only support vertex, but got %s, which is type %s", - val.toString().c_str(), - val.typeName().c_str()); - } - if (unique.emplace(val).second) { - Row row; - row.values.emplace_back(val); - ds.rows.emplace_back(std::move(row)); + + if (argNode->isInputVertexRequired()) { + if (!val.isVertex()) { + return Status::Error("Argument only support vertex, but got %s, whose type is '%s'", + val.toString().c_str(), + val.typeName().c_str()); + } + addRow(val); + } else { + if (val.isList()) { + for (auto &v : val.getList().values) { + addRow(v); + } + } else { + addRow(val); + } } } return finish(ResultBuilder().value(Value(std::move(ds))).build()); diff --git a/src/graph/optimizer/Optimizer.cpp b/src/graph/optimizer/Optimizer.cpp index 138503e67b2..e37f8f3f300 100644 --- a/src/graph/optimizer/Optimizer.cpp +++ b/src/graph/optimizer/Optimizer.cpp @@ -46,10 +46,7 @@ StatusOr Optimizer::findBestPlan(QueryContext *qctx) { NG_RETURN_IF_ERROR(doExploration(optCtx.get(), rootGroup)); auto *newRoot = rootGroup->getPlan(); - auto status2 = postprocess(const_cast(newRoot), qctx, spaceID); - if (!status2.ok()) { - DLOG(ERROR) << "Failed to postprocess plan: " << status2; - } + NG_RETURN_IF_ERROR(postprocess(const_cast(newRoot), qctx, spaceID)); return newRoot; } @@ -61,7 +58,8 @@ Status Optimizer::postprocess(PlanNode *root, graph::QueryContext *qctx, GraphSp graph::PrunePropertiesVisitor visitor(propsUsed, qctx, spaceID); root->accept(&visitor); if (!visitor.ok()) { - return visitor.status(); + LOG(INFO) << "Failed to prune properties of query plan in post process of optimizer: " + << visitor.status(); } } return Status::OK(); @@ -175,7 +173,13 @@ Status Optimizer::rewriteArgumentInputVarInternal(PlanNode *root, case 0: { if (root->kind() == PlanNode::Kind::kArgument) { if (!findArgumentRefPlanNodeInPath(path, root) || root->inputVar().empty()) { - return Status::Error("Could not find the right input variable for argument plan node"); + DCHECK(!root->outputVarPtr()->colNames.empty()); + auto outColumn = root->outputVarPtr()->colNames.back(); + return Status::Error( + "Could not generate valid query plan since the argument plan node could not find its " + "input data, please review your query and pay attention to the symbol `%s` usage " + "especially.", + outColumn.c_str()); } } break; diff --git a/src/graph/planner/CMakeLists.txt b/src/graph/planner/CMakeLists.txt index e8b0340130d..847effc7ed5 100644 --- a/src/graph/planner/CMakeLists.txt +++ b/src/graph/planner/CMakeLists.txt @@ -26,6 +26,7 @@ nebula_add_library( match/ArgumentFinder.cpp match/MatchPathPlanner.cpp match/ShortestPathPlanner.cpp + match/VariableVertexIdSeek.cpp ngql/PathPlanner.cpp ngql/GoPlanner.cpp ngql/SubgraphPlanner.cpp diff --git a/src/graph/planner/PlannersRegister.cpp b/src/graph/planner/PlannersRegister.cpp index 046a84e6d39..8002d11b02a 100644 --- a/src/graph/planner/PlannersRegister.cpp +++ b/src/graph/planner/PlannersRegister.cpp @@ -13,6 +13,7 @@ #include "graph/planner/match/PropIndexSeek.h" #include "graph/planner/match/ScanSeek.h" #include "graph/planner/match/StartVidFinder.h" +#include "graph/planner/match/VariableVertexIdSeek.h" #include "graph/planner/match/VertexIdSeek.h" #include "graph/planner/ngql/FetchEdgesPlanner.h" #include "graph/planner/ngql/FetchVerticesPlanner.h" @@ -99,6 +100,9 @@ void PlannersRegister::registerMatch() { // MATCH(n:Tag) WHERE n.prop = value RETURN n startVidFinders.emplace_back(&PropIndexSeek::make); + // WITH 'xxx' AS vid MATCH(n) WHERE id(n)==vid RETURN n + startVidFinders.emplace_back(&VariableVertexIdSeek::make); + // seek by tag or edge(index) // MATCH(n: tag) RETURN n // MATCH(s)-[:edge]->(e) RETURN e diff --git a/src/graph/planner/match/StartVidFinder.h b/src/graph/planner/match/StartVidFinder.h index 531d9dc54f1..3e93dc2e509 100644 --- a/src/graph/planner/match/StartVidFinder.h +++ b/src/graph/planner/match/StartVidFinder.h @@ -30,12 +30,16 @@ using StartVidFinderInstantiateFunc = std::function(e) RETURN e // -// 5. ScanSeek finds if a plan could traverse from some vids by scanning. +// 6. ScanSeek finds if a plan could traverse from some vids by scanning. +// class StartVidFinder { public: virtual ~StartVidFinder() = default; @@ -49,23 +53,32 @@ class StartVidFinder { // The derived class should implement matchNode if the finder has // the ability to find vids from node pattern. - virtual bool matchNode(NodeContext* nodeCtx) = 0; + virtual bool matchNode(NodeContext* /* nodeCtx */) { + return false; + } // The derived class should implement matchEdge if the finder has // the ability to find vids from edge pattern. - virtual bool matchEdge(EdgeContext* nodeCtx) = 0; + virtual bool matchEdge(EdgeContext* /* edgeCtx */) { + return false; + } StatusOr transform(PatternContext* patternCtx); - virtual StatusOr transformNode(NodeContext* nodeCtx) = 0; + virtual StatusOr transformNode(NodeContext* /* nodeCtx */) { + return Status::Error("Unimplemented"); + } - virtual StatusOr transformEdge(EdgeContext* edgeCtx) = 0; + virtual StatusOr transformEdge(EdgeContext* /* edgeCtx */) { + return Status::Error("Unimplemented"); + } virtual const char* name() const = 0; protected: StartVidFinder() = default; }; + } // namespace graph } // namespace nebula #endif // GRAPH_PLANNER_MATCH_STARTVIDFINDER_H_ diff --git a/src/graph/planner/match/VariableVertexIdSeek.cpp b/src/graph/planner/match/VariableVertexIdSeek.cpp new file mode 100644 index 00000000000..9acc6cd622e --- /dev/null +++ b/src/graph/planner/match/VariableVertexIdSeek.cpp @@ -0,0 +1,127 @@ +/* Copyright (c) 2023 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/planner/match/VariableVertexIdSeek.h" + +#include "graph/planner/plan/Logic.h" +#include "graph/planner/plan/Query.h" +#include "graph/util/ExpressionUtils.h" + +namespace nebula { +namespace graph { + +bool VariableVertexIdSeek::matchNode(NodeContext *nodeCtx) { + auto whereClause = nodeCtx->bindWhereClause; + if (!whereClause || !whereClause->filter) { + return false; + } + + auto nodeInfo = nodeCtx->info; + if (nodeInfo->alias.empty() || nodeInfo->anonymous) { + // require one named node + return false; + } + + Expression *vidPredicate = whereClause->filter; + std::string refVarName; + if (!extractVidPredicate(nodeInfo->alias, vidPredicate, &refVarName)) { + return false; + } + + // exclude the case where `refVarName` is from the path pattern of current match + if (!nodeCtx->nodeAliasesAvailable->count(refVarName)) { + return false; + } + + nodeCtx->refVarName = refVarName; + return true; +} + +StatusOr VariableVertexIdSeek::transformNode(NodeContext *nodeCtx) { + auto *qctx = nodeCtx->qctx; + const auto &refVarName = nodeCtx->refVarName; + DCHECK(!refVarName.empty()); + + SubPlan plan; + auto argument = Argument::make(qctx, refVarName); + argument->setColNames({refVarName}); + argument->setInputVertexRequired(false); + plan.root = plan.tail = argument; + + nodeCtx->initialExpr = InputPropertyExpression::make(qctx->objPool(), refVarName); + return plan; +} + +bool VariableVertexIdSeek::extractVidPredicate(const std::string &nodeAlias, + Expression *filter, + std::string *var) { + switch (filter->kind()) { + case Expression::Kind::kRelEQ: + case Expression::Kind::kRelIn: { + return isVidPredicate(nodeAlias, filter, var); + } + case Expression::Kind::kLogicalAnd: { + filter = filter->clone(); + ExpressionUtils::pullAnds(filter); + auto logicAndExpr = static_cast(filter); + for (auto operand : logicAndExpr->operands()) { + if (isVidPredicate(nodeAlias, operand, var)) { + return true; + } + } + return false; + } + default: { + return false; + } + } +} + +bool VariableVertexIdSeek::isVidPredicate(const std::string &nodeAlias, + const Expression *filter, + std::string *var) { + if (filter->kind() != Expression::Kind::kRelEQ && filter->kind() != Expression::Kind::kRelIn) { + return false; + } + + auto relExpr = static_cast(filter); + auto checkFunCall = [var, &nodeAlias](const Expression *expr, const Expression *varExpr) { + if (isIdFunCallExpr(nodeAlias, expr) && varExpr->kind() == Expression::Kind::kLabel) { + *var = static_cast(varExpr)->name(); + return true; + } + return false; + }; + + if (relExpr->left()->kind() == Expression::Kind::kFunctionCall) { + return checkFunCall(relExpr->left(), relExpr->right()); + } + + if (relExpr->right()->kind() == Expression::Kind::kFunctionCall) { + return checkFunCall(relExpr->right(), relExpr->left()); + } + + return false; +} + +bool VariableVertexIdSeek::isIdFunCallExpr(const std::string &nodeAlias, const Expression *filter) { + if (filter->kind() == Expression::Kind::kFunctionCall) { + auto funCallExpr = static_cast(filter); + if (funCallExpr->name() == "id") { + DCHECK_EQ(funCallExpr->args()->numArgs(), 1u); + auto arg = funCallExpr->args()->args()[0]; + if (arg->kind() == Expression::Kind::kLabel) { + auto labelExpr = static_cast(arg); + if (labelExpr->name() == nodeAlias) { + return true; + } + } + } + } + return false; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/planner/match/VariableVertexIdSeek.h b/src/graph/planner/match/VariableVertexIdSeek.h new file mode 100644 index 00000000000..496cc6a84f0 --- /dev/null +++ b/src/graph/planner/match/VariableVertexIdSeek.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2023 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_PLANNER_MATCH_VARIABLEVERTEXIDSEEK_H_ +#define GRAPH_PLANNER_MATCH_VARIABLEVERTEXIDSEEK_H_ + +#include "graph/context/ast/CypherAstContext.h" +#include "graph/planner/match/StartVidFinder.h" + +namespace nebula { +namespace graph { + +// The VariableVertexIdSeek find if a plan could get the starting vids in filters. +class VariableVertexIdSeek final : public StartVidFinder { + public: + static std::unique_ptr make() { + return std::unique_ptr(new VariableVertexIdSeek()); + } + + bool matchNode(NodeContext* nodeCtx) override; + + StatusOr transformNode(NodeContext* nodeCtx) override; + + const char* name() const override { + return "VariableIdVertexSeekFinder"; + } + + private: + VariableVertexIdSeek() = default; + + bool extractVidPredicate(const std::string& nodeAlias, Expression* filter, std::string* var); + static bool isVidPredicate(const std::string& nodeAlias, + const Expression* filter, + std::string* var); + static bool isIdFunCallExpr(const std::string& nodeAlias, const Expression* filter); +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_PLANNER_MATCH_VARIABLEVERTEXIDSEEK_H_ diff --git a/src/graph/planner/ngql/GoPlanner.cpp b/src/graph/planner/ngql/GoPlanner.cpp index 253e47244ac..4721c1e8d3a 100644 --- a/src/graph/planner/ngql/GoPlanner.cpp +++ b/src/graph/planner/ngql/GoPlanner.cpp @@ -111,6 +111,7 @@ PlanNode* GoPlanner::buildJoinDstPlan(PlanNode* dep) { auto& colName = dep->colNames().back(); auto argNode = Argument::make(qctx, colName); argNode->setColNames({colName}); + argNode->setInputVertexRequired(false); // dst is the first column, columnName is "JOIN_DST_VID" auto* dstExpr = ColumnExpression::make(pool, LAST_COL_INDEX); @@ -246,6 +247,7 @@ StatusOr GoPlanner::transform(AstContext* astCtx) { auto argNode = Argument::make(qctx, from.runtimeVidName); argNode->setColNames({from.runtimeVidName}); + argNode->setInputVertexRequired(false); goCtx_->vidsVar = argNode->outputVar(); startNode_ = argNode; } diff --git a/src/graph/planner/plan/Logic.cpp b/src/graph/planner/plan/Logic.cpp index 824af27ebc9..e986370497e 100644 --- a/src/graph/planner/plan/Logic.cpp +++ b/src/graph/planner/plan/Logic.cpp @@ -83,11 +83,15 @@ PlanNode* Argument::clone() const { void Argument::cloneMembers(const Argument& arg) { PlanNode::cloneMembers(arg); + alias_ = arg.getAlias(); + isInputVertexRequired_ = arg.isInputVertexRequired(); } std::unique_ptr Argument::explain() const { auto desc = PlanNode::explain(); addDescription("inputVar", inputVar(), desc.get()); + addDescription("alias", alias_, desc.get()); + addDescription("isInputVertexRequired", std::to_string(isInputVertexRequired_), desc.get()); return desc; } } // namespace graph diff --git a/src/graph/planner/plan/Logic.h b/src/graph/planner/plan/Logic.h index f503bf1aae5..4b103a2aacb 100644 --- a/src/graph/planner/plan/Logic.h +++ b/src/graph/planner/plan/Logic.h @@ -42,7 +42,7 @@ class BinarySelect : public SingleInputNode { void cloneMembers(const BinarySelect& s) { SingleInputNode::cloneMembers(s); - condition_ = s.condition(); + condition_ = s.condition()->clone(); } protected: @@ -153,6 +153,14 @@ class Argument final : public PlanNode { return qctx->objPool()->makeAndAdd(qctx, alias); } + bool isInputVertexRequired() const { + return isInputVertexRequired_; + } + + void setInputVertexRequired(bool required) { + isInputVertexRequired_ = required; + } + PlanNode* clone() const override; const std::string& getAlias() const { @@ -169,7 +177,9 @@ class Argument final : public PlanNode { private: std::string alias_; + bool isInputVertexRequired_{true}; }; + } // namespace graph } // namespace nebula diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index d4517705809..7071cd85219 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -1333,8 +1333,7 @@ Status MatchValidator::validatePathInWhere( if (!edgeAlias.empty() && !AnonVarGenerator::isAnnoVar(edgeAlias)) { if (edge->range()) { return Status::SemanticError( - "Variable '%s` 's type is edge list. not support used in multiple patterns " - "simultaneously.", + "Unsupported type of variable '%s` used in multiple patterns: list of edges.", edgeAlias.c_str()); } pathInfo.compareVariables.emplace_back(edgeAlias); diff --git a/tests/tck/features/match/MatchByVariable.feature b/tests/tck/features/match/MatchByVariable.feature new file mode 100644 index 00000000000..c3ea120031f --- /dev/null +++ b/tests/tck/features/match/MatchByVariable.feature @@ -0,0 +1,805 @@ +# Copyright (c) 2023 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Match By Variable + + Background: + Given a graph with space named "nba" + + Scenario: [1] match by vids from with + When profiling query: + """ + with ['Tim Duncan', 'Yao Ming'] as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 20 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 9 | Aggregate | 8 | | | + | 8 | Filter | 7 | | | + | 7 | CrossJoin | 1,6 | | | + | 1 | Project | 2 | | | + | 2 | Start | | | | + | 6 | Project | 11 | | | + | 11 | AppendVertices | 10 | | | + | 10 | Traverse | 3 | | | + | 3 | Argument | | | | + When profiling query: + """ + with ['Tim Duncan', 'Yao Ming'] as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND id(v2) in ['Tony Parker'] + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 11 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Project | 2 | | | + | 2 | Start | | | | + | 17 | Project | 21 | | | + | 21 | AppendVertices | 20 | | | + | 20 | Filter | 18 | | | + | 18 | Traverse | 4 | | | + | 4 | Dedup | 3 | | | + | 3 | PassThrough | 5 | | | + | 5 | Start | | | | + When profiling query: + """ + with ['Tim Duncan', 'Yao Ming'] as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND id(v2) in id_list + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 9 | Aggregate | 8 | | | + | 8 | Filter | 7 | | | + | 7 | CrossJoin | 1,6 | | | + | 1 | Project | 2 | | | + | 2 | Start | | | | + | 6 | Project | 11 | | | + | 11 | AppendVertices | 10 | | | + | 10 | Traverse | 3 | | | + | 3 | Argument | | | | + When profiling query: + """ + with ['Tim Duncan', 'Yao Ming'] as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 10 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Project | 2 | | | + | 2 | Start | | | | + | 17 | Project | 16 | | | + | 16 | Filter | 19 | | | + | 19 | AppendVertices | 18 | | | + | 18 | Traverse | 11 | | | + | 11 | IndexScan | 4 | | | + | 4 | Start | | | | + When profiling query: + """ + with ['Tim Duncan', 'Yao Ming'] as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 10 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Project | 2 | | | + | 2 | Start | | | | + | 17 | Project | 16 | | | + | 16 | Filter | 19 | | | + | 19 | AppendVertices | 18 | | | + | 18 | Traverse | 11 | | | + | 11 | IndexScan | 4 | | | + | 4 | Start | | | | + + Scenario: [2] match by vids from with + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 16 | | | + | 16 | AppendVertices | 14 | | | + | 14 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 18 | | | + | 18 | AppendVertices | 17 | | | + | 17 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list and id(v2) in ['Tony Parker'] + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 15 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 16 | | | + | 16 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 28 | | | + | 28 | AppendVertices | 27 | | | + | 27 | Filter | 25 | | | + | 25 | Traverse | 8 | | | + | 8 | Dedup | 7 | | | + | 7 | PassThrough | 9 | | | + | 9 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND id(v2) in id_list + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: ((id($v1) IN $id_list) AND (id($v2) IN $id_list)). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 16 | | | + | 16 | AppendVertices | 14 | | | + | 14 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 18 | | | + | 18 | AppendVertices | 17 | | | + | 17 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 15 | | | + | 15 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 22 | | | + | 22 | Filter | 26 | | | + | 26 | AppendVertices | 25 | | | + | 25 | Traverse | 16 | | | + | 16 | IndexScan | 8 | | | + | 8 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 15 | | | + | 15 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 22 | | | + | 22 | Filter | 26 | | | + | 26 | AppendVertices | 25 | | | + | 25 | Traverse | 16 | | | + | 16 | IndexScan | 8 | | | + | 8 | Start | | | | + + Scenario: [3] match by vids from with + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 15 | | | + | 15 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 17 | | | + | 17 | AppendVertices | 16 | | | + | 16 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list and id(v2) in ['Tim Duncan', 'Yao Ming'] + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 15 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 27 | | | + | 27 | AppendVertices | 26 | | | + | 26 | Filter | 24 | | | + | 24 | Traverse | 8 | | | + | 8 | Dedup | 7 | | | + | 7 | PassThrough | 9 | | | + | 9 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND id(v2) in id_list + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: ((id($v1) IN $id_list) AND (id($v2) IN $id_list)). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 15 | | | + | 15 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 17 | | | + | 17 | AppendVertices | 16 | | | + | 16 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 21 | | | + | 21 | Filter | 25 | | | + | 25 | AppendVertices | 24 | | | + | 24 | Traverse | 15 | | | + | 15 | IndexScan | 8 | | | + | 8 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as id_list + match (v1:player)-[e]-(v2:player) + where id(v1) in id_list AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($v1) IN $id_list). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 21 | | | + | 21 | Filter | 25 | | | + | 25 | AppendVertices | 24 | | | + | 24 | Traverse | 15 | | | + | 15 | IndexScan | 8 | | | + | 8 | Start | | | | + + Scenario: [1] match by vids from unwind + When profiling query: + """ + unwind ['Tim Duncan', 'Yao Ming'] as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 20 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 9 | Aggregate | 8 | | | + | 8 | Filter | 7 | | | + | 7 | CrossJoin | 1,6 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 6 | Project | 11 | | | + | 11 | AppendVertices | 10 | | | + | 10 | Traverse | 3 | | | + | 3 | Argument | | | | + When profiling query: + """ + unwind ['Tim Duncan', 'Yao Ming'] as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid and id(v2)=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 11 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 17 | Project | 21 | | | + | 21 | AppendVertices | 20 | | | + | 20 | Filter | 18 | | | + | 18 | Traverse | 4 | | | + | 4 | Dedup | 3 | | | + | 3 | PassThrough | 5 | | | + | 5 | Start | | | | + When profiling query: + """ + unwind ['Tim Duncan', 'Yao Ming'] as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND id(v2)==vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 9 | Aggregate | 8 | | | + | 8 | Filter | 7 | | | + | 7 | CrossJoin | 1,6 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 6 | Project | 11 | | | + | 11 | AppendVertices | 10 | | | + | 10 | Traverse | 3 | | | + | 3 | Argument | | | | + When profiling query: + """ + unwind ['Tim Duncan', 'Yao Ming'] as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 10 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 17 | Project | 16 | | | + | 16 | Filter | 19 | | | + | 19 | AppendVertices | 18 | | | + | 18 | Traverse | 11 | | | + | 11 | IndexScan | 4 | | | + | 4 | Start | | | | + When profiling query: + """ + unwind ['Tim Duncan', 'Yao Ming'] as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 10 | Aggregate | 15 | | | + | 15 | Filter | 14 | | | + | 14 | CrossJoin | 1,17 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 17 | Project | 16 | | | + | 16 | Filter | 19 | | | + | 19 | AppendVertices | 18 | | | + | 18 | Traverse | 11 | | | + | 11 | IndexScan | 4 | | | + | 4 | Start | | | | + + Scenario: [2] match by vids from unwind + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1) == vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 2 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 16 | | | + | 16 | AppendVertices | 14 | | | + | 14 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 18 | | | + | 18 | AppendVertices | 17 | | | + | 17 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1) == vid and id(v2)=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 15 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 16 | | | + | 16 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 28 | | | + | 28 | AppendVertices | 27 | | | + | 27 | Filter | 25 | | | + | 25 | Traverse | 8 | | | + | 8 | Dedup | 7 | | | + | 7 | PassThrough | 9 | | | + | 9 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND id(v2)==vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 16 | | | + | 16 | AppendVertices | 14 | | | + | 14 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 18 | | | + | 18 | AppendVertices | 17 | | | + | 17 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 15 | | | + | 15 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 22 | | | + | 22 | Filter | 26 | | | + | 26 | AppendVertices | 25 | | | + | 25 | Traverse | 16 | | | + | 16 | IndexScan | 8 | | | + | 8 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name=='Yao Ming' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 20 | | | + | 20 | Filter | 19 | | | + | 19 | CrossJoin | 6,23 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 24 | | | + | 24 | AppendVertices | 15 | | | + | 15 | IndexScan | 2 | | | + | 2 | Start | | | | + | 23 | Project | 22 | | | + | 22 | Filter | 26 | | | + | 26 | AppendVertices | 25 | | | + | 25 | Traverse | 16 | | | + | 16 | IndexScan | 8 | | | + | 8 | Start | | | | + + Scenario: [3] match by vids from unwind + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 40 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 15 | | | + | 15 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 17 | | | + | 17 | AppendVertices | 16 | | | + | 16 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid and id(v2)=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 15 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 27 | | | + | 27 | AppendVertices | 26 | | | + | 26 | Filter | 24 | | | + | 24 | Traverse | 8 | | | + | 8 | Dedup | 7 | | | + | 7 | PassThrough | 9 | | | + | 9 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND id(v2)==vid + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 0 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 13 | Aggregate | 12 | | | + | 12 | Filter | 11 | | | + | 11 | CrossJoin | 6,10 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 15 | | | + | 15 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 10 | Project | 17 | | | + | 17 | AppendVertices | 16 | | | + | 16 | Traverse | 7 | | | + | 7 | Argument | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v1.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 14 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 21 | | | + | 21 | Filter | 25 | | | + | 25 | AppendVertices | 24 | | | + | 24 | Traverse | 15 | | | + | 15 | IndexScan | 8 | | | + | 8 | Start | | | | + When profiling query: + """ + match (v:player) + where v.player.name starts with 'T' + with id(v) as vid + match (v1:player)-[e]-(v2:player) + where id(v1)==vid AND v2.player.name=='Tony Parker' + return count(*) AS n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 4 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 14 | Aggregate | 19 | | | + | 19 | Filter | 18 | | | + | 18 | CrossJoin | 6,22 | | | + | 6 | Project | 5 | | | + | 5 | Filter | 23 | | | + | 23 | AppendVertices | 1 | | | + | 1 | IndexScan | 2 | | | + | 2 | Start | | | | + | 22 | Project | 21 | | | + | 21 | Filter | 25 | | | + | 25 | AppendVertices | 24 | | | + | 24 | Traverse | 15 | | | + | 15 | IndexScan | 8 | | | + | 8 | Start | | | | + + Scenario: unwind with and match + When profiling query: + """ + unwind [1, 2] AS x + with ['Tim Duncan', 'Yao Ming'] AS id_list + match (v1:player)--(v2) + where id(v1) IN id_list + return count(*) as n + """ + Then the result should be, in any order, with relax comparison: + | n | + | 44 | + And the execution plan should be: + | id | name | dependencies | profiling data | operator info | + | 10 | Aggregate | 9 | | | + | 9 | Filter | 8 | | | + | 8 | CrossJoin | 3,7 | | | + | 3 | Project | 1 | | | + | 1 | Unwind | 2 | | | + | 2 | Start | | | | + | 7 | Project | 6 | | | + | 6 | AppendVertices | 11 | | | + | 11 | Traverse | 4 | | | + | 4 | Argument | | | | + + Scenario: invalid variable in id condition + When executing query: + """ + match (v1:player)--(v2) + where id(v1) IN v2 + return count(*) as n + """ + Then a ExecutionError should be raised at runtime: Failed to evaluate condition: (id($-.v1) IN $-.v2). For boolean conditions, please write in their full forms like == or IS [NOT] NULL. diff --git a/tests/tck/features/match/PathExpr.feature b/tests/tck/features/match/PathExpr.feature index c419d377f79..2a06d2c3882 100644 --- a/tests/tck/features/match/PathExpr.feature +++ b/tests/tck/features/match/PathExpr.feature @@ -448,7 +448,7 @@ Feature: Basic match MATCH (v:player{name: 'Tim Duncan'})-[e:like*2]->(n) RETURN ()-[e:like*2]->(n) """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player{name: 'Tim Duncan'})-[e:like*3]->(n), (t:team {name: "Spurs"}) @@ -456,24 +456,24 @@ Feature: Basic match UNWIND [n in ns | ()-[e*3]->(n:player)] AS p RETURN p """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player)-[e:like*3]->(n) WHERE (n)-[e*3]->(:player) RETURN v """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player)-[e:like*1..3]->(n) WHERE (n)-[e*1..4]->(:player) return v """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player)-[e:like*3]->(n) WHERE id(v)=="Tim Duncan" and (n)-[e*3]->(:player) return v """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player)-[e:like]->(n) WHERE (n)-[e*1..4]->(:player) RETURN v @@ -483,7 +483,7 @@ Feature: Basic match """ MATCH (v:player)-[e:like*3]->(n) WHERE (n)-[e*1]->(:player) RETURN v """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. When executing query: """ MATCH (v:player)-[e:like*1..3]->(n) WHERE (n)-[e]->(:player) RETURN v diff --git a/tests/tck/features/match/PathExprRefLocalVariable.feature b/tests/tck/features/match/PathExprRefLocalVariable.feature index 44c41555586..34e45050c96 100644 --- a/tests/tck/features/match/PathExprRefLocalVariable.feature +++ b/tests/tck/features/match/PathExprRefLocalVariable.feature @@ -296,4 +296,4 @@ Feature: Path expression reference local defined variables """ MATCH (v:player{name: 'Tim Duncan'})-[e:like*1..3]->(n), (t:team {name: "Spurs"}) WITH v, e, collect(distinct n) AS ns UNWIND [n in ns | ()-[e*2..4]->(n:player)] AS p RETURN count(p) AS count """ - Then a SemanticError should be raised at runtime: Variable 'e` 's type is edge list. not support used in multiple patterns simultaneously. + Then a SemanticError should be raised at runtime: Unsupported type of variable 'e` used in multiple patterns: list of edges. diff --git a/tests/tck/features/match/With.feature b/tests/tck/features/match/With.feature index d9983e4620a..b1a0a3b7a04 100644 --- a/tests/tck/features/match/With.feature +++ b/tests/tck/features/match/With.feature @@ -453,27 +453,27 @@ Feature: With clause """ with "1" as a match (a)-[e:like]->(b) return b """ - Then a ExecutionError should be raised at runtime: Argument only support vertex, but got "1", which is type string + Then a ExecutionError should be raised at runtime: Argument only support vertex, but got "1", whose type is 'string' When executing query: """ with 1 as a match (a)-[e:like]->(b) return b """ - Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, which is type int + Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, whose type is 'int' When executing query: """ with 1 as b match (b) return b """ - Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, which is type int + Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, whose type is 'int' When executing query: """ with [1,2] as a unwind a as b match (b) return b """ - Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, which is type int + Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, whose type is 'int' When executing query: """ with [1,2] as a unwind a as b match (b)-[e:like]->(a:player{age:30}) return b """ - Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, which is type int + Then a ExecutionError should be raised at runtime: Argument only support vertex, but got 1, whose type is 'int' Scenario: duplicate columns When executing query: