diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h index 984d3f753..a76c5170a 100644 --- a/src/parser/Clauses.h +++ b/src/parser/Clauses.h @@ -387,5 +387,47 @@ using InBoundClause = BoundClause; using OutBoundClause = BoundClause; using BothInOutClause = BoundClause; +class NameLabelList { +public: + NameLabelList() = default; + + void add(std::string *label) { + labels_.emplace_back(label); + } + + bool empty() const { + return labels_.empty(); + } + + std::size_t size() const { + return labels_.size(); + } + + const std::string* front() const { + return labels_.front().get(); + } + + std::vector labels() const { + std::vector labels; + labels.reserve(labels_.size()); + for (const auto& it : labels_) { + labels.emplace_back(it.get()); + } + return labels; + } + + std::string toString() const { + std::stringstream ss; + for (std::size_t i = 0; i < labels_.size() - 1; ++i) { + ss << *labels_[i].get() << ","; + } + ss << *labels_.back(); + return ss.str(); + } + +private: + std::vector> labels_; +}; + } // namespace nebula #endif // PARSER_CLAUSES_H_ diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp index 2206ff776..51bdb8163 100644 --- a/src/parser/TraverseSentences.cpp +++ b/src/parser/TraverseSentences.cpp @@ -128,7 +128,7 @@ std::string FetchVerticesSentence::toString() const { std::string buf; buf.reserve(256); buf += "FETCH PROP ON "; - buf += *tag_; + buf += tags_->toString(); buf += " "; if (isRef()) { buf += vidRef_->toString(); @@ -146,7 +146,7 @@ std::string FetchEdgesSentence::toString() const { std::string buf; buf.reserve(256); buf += "FETCH PROP ON "; - buf += *edge_; + buf += edge_->toString(); buf += " "; if (isRef()) { buf += keyRef_->toString(); diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h index f4a458d17..3740a4119 100644 --- a/src/parser/TraverseSentences.h +++ b/src/parser/TraverseSentences.h @@ -297,44 +297,44 @@ class OrderBySentence final : public Sentence { class FetchVerticesSentence final : public Sentence { public: - FetchVerticesSentence(std::string *tag, + FetchVerticesSentence(NameLabelList *tags, VertexIDList *vidList, YieldClause *clause) { kind_ = Kind::kFetchVertices; - tag_.reset(tag); + tags_.reset(tags); vidList_.reset(vidList); yieldClause_.reset(clause); } - FetchVerticesSentence(std::string *tag, + FetchVerticesSentence(NameLabelList *tags, Expression *ref, YieldClause *clause) { kind_ = Kind::kFetchVertices; - tag_.reset(tag); + tags_.reset(tags); vidRef_.reset(ref); yieldClause_.reset(clause); } explicit FetchVerticesSentence(Expression *ref, YieldClause *clause) { kind_ = Kind::kFetchVertices; - tag_ = std::make_unique("*"); + tags_ = std::make_unique(); vidRef_.reset(ref); yieldClause_.reset(clause); } explicit FetchVerticesSentence(VertexIDList *vidList, YieldClause *clause) { kind_ = Kind::kFetchVertices; - tag_ = std::make_unique("*"); + tags_ = std::make_unique(); vidList_.reset(vidList); yieldClause_.reset(clause); } bool isAllTagProps() { - return *tag_ == "*"; + return tags_->empty(); } - auto tag() const { - return tag_.get(); + const NameLabelList* tags() const { + return tags_.get(); } auto vidList() const { @@ -360,7 +360,7 @@ class FetchVerticesSentence final : public Sentence { std::string toString() const override; private: - std::unique_ptr tag_; + std::unique_ptr tags_; std::unique_ptr vidList_; std::unique_ptr vidRef_; std::unique_ptr yieldClause_; @@ -368,18 +368,18 @@ class FetchVerticesSentence final : public Sentence { class FetchEdgesSentence final : public Sentence { public: - FetchEdgesSentence(std::string *edge, - EdgeKeys *keys, - YieldClause *clause) { + FetchEdgesSentence(NameLabelList *edge, + EdgeKeys *keys, + YieldClause *clause) { kind_ = Kind::kFetchEdges; edge_.reset(edge); edgeKeys_.reset(keys); yieldClause_.reset(clause); } - FetchEdgesSentence(std::string *edge, - EdgeKeyRef *ref, - YieldClause *clause) { + FetchEdgesSentence(NameLabelList *edge, + EdgeKeyRef *ref, + YieldClause *clause) { kind_ = Kind::kFetchEdges; edge_.reset(edge); keyRef_.reset(ref); @@ -414,14 +414,18 @@ class FetchEdgesSentence final : public Sentence { return yieldClause_.get(); } - std::string* edge() const { - return edge_.get(); + const std::string* edge() const { + return edge_->front(); + } + + std::size_t edgeSize() const { + return edge_->size(); } std::string toString() const override; private: - std::unique_ptr edge_; + std::unique_ptr edge_; std::unique_ptr edgeKeys_; std::unique_ptr keyRef_; std::unique_ptr yieldClause_; diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 1f05d9396..898f9e681 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -62,6 +62,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; nebula::FromClause *from_clause; nebula::ToClause *to_clause; nebula::VertexIDList *vid_list; + nebula::NameLabelList *name_label_list; nebula::OverEdge *over_edge; nebula::OverEdges *over_edges; nebula::OverClause *over_clause; @@ -275,6 +276,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type role_type_clause %type acl_item_clause +%type name_label_list %type index_field %type index_field_list @@ -343,6 +345,17 @@ name_label | unreserved_keyword { $$ = $1; } ; +name_label_list + : name_label { + $$ = new NameLabelList(); + $$->add($1); + } + | name_label_list COMMA name_label { + $1->add($3); + $$ = $1; + } + ; + legal_integer : INTEGER { ifOutOfRange($1, @1); @@ -1367,10 +1380,10 @@ order_by_sentence ; fetch_vertices_sentence - : KW_FETCH KW_PROP KW_ON name_label vid_list yield_clause { + : KW_FETCH KW_PROP KW_ON name_label_list vid_list yield_clause { $$ = new FetchVerticesSentence($4, $5, $6); } - | KW_FETCH KW_PROP KW_ON name_label vid_ref_expression yield_clause { + | KW_FETCH KW_PROP KW_ON name_label_list vid_ref_expression yield_clause { $$ = new FetchVerticesSentence($4, $5, $6); } | KW_FETCH KW_PROP KW_ON STAR vid_list yield_clause { @@ -1421,11 +1434,11 @@ edge_key_ref: ; fetch_edges_sentence - : KW_FETCH KW_PROP KW_ON name_label edge_keys yield_clause { + : KW_FETCH KW_PROP KW_ON name_label_list edge_keys yield_clause { auto fetch = new FetchEdgesSentence($4, $5, $6); $$ = fetch; } - | KW_FETCH KW_PROP KW_ON name_label edge_key_ref yield_clause { + | KW_FETCH KW_PROP KW_ON name_label_list edge_key_ref yield_clause { auto fetch = new FetchEdgesSentence($4, $5, $6); $$ = fetch; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 22b7bf7f5..57549f03c 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -1156,6 +1156,30 @@ TEST(Parser, FetchVertex) { auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + { + GQLParser parser; + std::string query = "FETCH PROP ON * \"1\", \"2\""; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "FETCH PROP ON * $-.id"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "yield \"1\" as id | FETCH PROP ON * $-.id"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "yield \"1\" as id | FETCH PROP ON * $-.id yield friend.id, person.id"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } } TEST(Parser, FetchEdge) { @@ -1195,6 +1219,13 @@ TEST(Parser, FetchEdge) { auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + // this will be denied in Semantic Analysis + { + GQLParser parser; + std::string query = "FETCH PROP ON transfer, another \"12345\" -> \"-54321\""; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } } TEST(Parser, Lookup) { diff --git a/src/validator/FetchEdgesValidator.cpp b/src/validator/FetchEdgesValidator.cpp index fe8fcbd8b..8776d1ed0 100644 --- a/src/validator/FetchEdgesValidator.cpp +++ b/src/validator/FetchEdgesValidator.cpp @@ -77,6 +77,9 @@ Status FetchEdgesValidator::toPlan() { Status FetchEdgesValidator::check() { auto *sentence = static_cast(sentence_); + if (sentence->edgeSize() > 1) { + return Status::SemanticError("Only allow fetch on one edge."); + } spaceId_ = vctx_->whichSpace().id; edgeTypeName_ = *sentence->edge(); auto edgeStatus = qctx_->schemaMng()->toEdgeType(spaceId_, edgeTypeName_); diff --git a/src/validator/FetchVerticesValidator.cpp b/src/validator/FetchVerticesValidator.cpp index 5b1ef3e06..38f967647 100644 --- a/src/validator/FetchVerticesValidator.cpp +++ b/src/validator/FetchVerticesValidator.cpp @@ -65,18 +65,20 @@ Status FetchVerticesValidator::check() { if (!sentence->isAllTagProps()) { onStar_ = false; - auto tagName = *(sentence->tag()); - auto tagStatus = qctx_->schemaMng()->toTagID(space_.id, tagName); - NG_RETURN_IF_ERROR(tagStatus); - auto tagId = tagStatus.value(); + auto tags = sentence->tags()->labels(); + for (const auto& tag : tags) { + auto tagStatus = qctx_->schemaMng()->toTagID(space_.id, *tag); + NG_RETURN_IF_ERROR(tagStatus); + auto tagId = tagStatus.value(); - tags_.emplace(tagName, tagId); - auto schema = qctx_->schemaMng()->getTagSchema(space_.id, tagId); - if (schema == nullptr) { - LOG(ERROR) << "No schema found for " << tagName; - return Status::SemanticError("No schema found for `%s'", tagName.c_str()); + tags_.emplace(*tag, tagId); + auto schema = qctx_->schemaMng()->getTagSchema(space_.id, tagId); + if (schema == nullptr) { + LOG(ERROR) << "No schema found for " << *tag; + return Status::SemanticError("No schema found for `%s'", tag->c_str()); + } + tagsSchema_.emplace(tagId, schema); } - tagsSchema_.emplace(tagId, schema); } else { onStar_ = true; const auto allTagsResult = qctx_->schemaMng()->getAllVerTagSchema(space_.id); diff --git a/src/validator/test/FetchEdgesTest.cpp b/src/validator/test/FetchEdgesTest.cpp index d4b56916b..07d192b97 100644 --- a/src/validator/test/FetchEdgesTest.cpp +++ b/src/validator/test/FetchEdgesTest.cpp @@ -418,6 +418,9 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesPropFailed) { ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" YIELD $^.like.start + 1")); ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" YIELD $$.like.start + 1")); + + // Fetch on multi-edges + ASSERT_FALSE(validate("FETCH PROP ON like, serve \"1\"->\"2\"")); } TEST_F(FetchEdgesValidatorTest, FetchEdgesInputFailed) { diff --git a/src/validator/test/FetchVerticesTest.cpp b/src/validator/test/FetchVerticesTest.cpp index 8933a66f6..21e17abbc 100644 --- a/src/validator/test/FetchVerticesTest.cpp +++ b/src/validator/test/FetchVerticesTest.cpp @@ -41,6 +41,35 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto result = Eq(qctx->plan()->root(), gv); ASSERT_TRUE(result.ok()) << result; } + // multi-tags + { + auto qctx = getQCtx("FETCH PROP ON person, book \"1\""); + + auto *start = StartNode::make(qctx); + + // person + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp personProp; + personProp.set_tag(tagId); + // book + tagIdResult = schemaMng_->toTagID(1, "book"); + ASSERT_TRUE(tagIdResult.ok()); + tagId = tagIdResult.value(); + storage::cpp2::VertexProp bookProp; + bookProp.set_tag(tagId); + auto *gv = GetVertices::make( + qctx, + start, + 1, + src.get(), + std::vector{std::move(personProp), std::move(bookProp)}, + {}); + gv->setColNames({"VertexID", "person.name", "person.age", "book.name"}); + auto result = Eq(qctx->plan()->root(), gv); + ASSERT_TRUE(result.ok()) << result; + } // With YIELD { auto qctx = getQCtx("FETCH PROP ON person \"1\" YIELD person.name, person.age"); @@ -82,6 +111,61 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto result = Eq(qctx->plan()->root(), project); ASSERT_TRUE(result.ok()) << result; } + // multi-tags With YIELD + { + auto qctx = + getQCtx("FETCH PROP ON person,book \"1\" YIELD person.name, person.age, book.name"); + + auto *start = StartNode::make(qctx); + + // person + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp personProp; + personProp.set_tag(tagId); + personProp.set_props(std::vector{"name", "age"}); + // book + tagIdResult = schemaMng_->toTagID(1, "book"); + ASSERT_TRUE(tagIdResult.ok()); + tagId = tagIdResult.value(); + storage::cpp2::VertexProp bookProp; + bookProp.set_tag(tagId); + bookProp.set_props(std::vector{"name"}); + 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()); + storage::cpp2::Expr expr3; + expr3.set_expr( + TagPropertyExpression(new std::string("book"), new std::string("name")).encode()); + auto *gv = GetVertices::make( + qctx, + start, + 1, + src.get(), + std::vector{std::move(personProp), std::move(bookProp)}, + std::vector{std::move(expr1), std::move(expr2), std::move(expr3)}); + gv->setColNames({"VertexID", "person.name", "person.age", "book.name"}); + + // project + auto yieldColumns = std::make_unique(); + yieldColumns->addColumn(new YieldColumn( + new InputPropertyExpression(new std::string("VertexID")), new std::string("VertexID"))); + 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")))); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("book"), new std::string("name")))); + auto *project = Project::make(qctx, gv, yieldColumns.get()); + project->setColNames({"VertexID", "person.name", "person.age", "book.name"}); + + auto result = Eq(qctx->plan()->root(), project); + ASSERT_TRUE(result.ok()) << result; + } // With YIELD const expression { auto qctx = getQCtx("FETCH PROP ON person \"1\" YIELD person.name, 1 > 1, person.age"); @@ -126,6 +210,65 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto result = Eq(qctx->plan()->root(), project); ASSERT_TRUE(result.ok()) << result; } + // multi-tags With YIELD const expression + { + auto qctx = getQCtx( + "FETCH PROP ON person, book \"1\" YIELD person.name, 1 > 1, book.name, person.age"); + + auto *start = StartNode::make(qctx); + + // get vertices + // person + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp personProp; + personProp.set_tag(tagId); + personProp.set_props(std::vector{"name", "age"}); + // book + tagIdResult = schemaMng_->toTagID(1, "book"); + ASSERT_TRUE(tagIdResult.ok()); + tagId = tagIdResult.value(); + storage::cpp2::VertexProp bookProp; + bookProp.set_tag(tagId); + bookProp.set_props(std::vector{"name"}); + + 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("book"), new std::string("name")).encode()); + storage::cpp2::Expr expr3; + expr3.set_expr( + TagPropertyExpression(new std::string("person"), new std::string("age")).encode()); + auto *gv = GetVertices::make( + qctx, + start, + 1, + src.get(), + std::vector{std::move(personProp), std::move(bookProp)}, + std::vector{std::move(expr1), std::move(expr2), std::move(expr3)}); + gv->setColNames({"VertexID", "person.name", "book.name", "person.age"}); + + // project + auto yieldColumns = std::make_unique(); + yieldColumns->addColumn(new YieldColumn( + new InputPropertyExpression(new std::string("VertexID")), new std::string("VertexID"))); + 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("book"), new std::string("name")))); + yieldColumns->addColumn(new YieldColumn( + new TagPropertyExpression(new std::string("person"), new std::string("age")))); + auto *project = Project::make(qctx, gv, yieldColumns.get()); + project->setColNames({"VertexID", "person.name", "(1>1)", "book.name", "person.age"}); + + auto result = Eq(qctx->plan()->root(), project); + ASSERT_TRUE(result.ok()) << result; + } // With YIELD combine properties { auto qctx = getQCtx("FETCH PROP ON person \"1\" YIELD person.name + person.age"); @@ -168,6 +311,58 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto result = Eq(qctx->plan()->root(), project); ASSERT_TRUE(result.ok()) << result; } + // multi-tags With YIELD combine properties + { + auto qctx = getQCtx("FETCH PROP ON book,person \"1\" YIELD person.name + book.name"); + + auto *start = StartNode::make(qctx); + + // person + auto tagIdResult = schemaMng_->toTagID(1, "person"); + ASSERT_TRUE(tagIdResult.ok()); + auto tagId = tagIdResult.value(); + storage::cpp2::VertexProp personProp; + personProp.set_tag(tagId); + personProp.set_props(std::vector{"name"}); + // book + tagIdResult = schemaMng_->toTagID(1, "book"); + ASSERT_TRUE(tagIdResult.ok()); + tagId = tagIdResult.value(); + storage::cpp2::VertexProp bookProp; + bookProp.set_tag(tagId); + bookProp.set_props(std::vector{"name"}); + + 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("book"), new std::string("name"))) + .encode()); + + auto *gv = GetVertices::make( + qctx, + start, + 1, + src.get(), + std::vector{std::move(personProp), std::move(bookProp)}, + std::vector{std::move(expr1)}); + gv->setColNames({"VertexID", "person.name", "book.name"}); + + // project, TODO(shylock) could push down to storage is it supported + auto yieldColumns = std::make_unique(); + yieldColumns->addColumn(new YieldColumn( + new InputPropertyExpression(new std::string("VertexID")), new std::string("VertexID"))); + yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression( + Expression::Kind::kAdd, + new TagPropertyExpression(new std::string("person"), new std::string("name")), + new TagPropertyExpression(new std::string("book"), new std::string("name"))))); + auto *project = Project::make(qctx, gv, yieldColumns.get()); + project->setColNames({"VertexID", "(person.name+book.name)"}); + + auto result = Eq(qctx->plan()->root(), project); + ASSERT_TRUE(result.ok()) << result; + } // With YIELD distinct { auto qctx = getQCtx("FETCH PROP ON person \"1\" YIELD distinct person.name, person.age"); @@ -247,7 +442,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto *start = StartNode::make(qctx); - std::vector colNames {"VertexID", "person.name"}; + std::vector colNames{"VertexID", "person.name"}; // Get vertices auto *gv = GetVertices::make(qctx, start, 1, src.get(), {}, {}); gv->setColNames(colNames); @@ -269,7 +464,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto *start = StartNode::make(qctx); - std::vector colNames {"VertexID", "person.name", "person.age"}; + std::vector colNames{"VertexID", "person.name", "person.age"}; // Get vertices auto *gv = GetVertices::make(qctx, start, 1, src.get(), {}, {}); gv->setColNames(colNames); @@ -301,10 +496,8 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) { auto yieldColumns = std::make_unique(); yieldColumns->addColumn(new YieldColumn( new InputPropertyExpression(new std::string("VertexID")), new std::string("VertexID"))); - yieldColumns->addColumn(new YieldColumn( - new ArithmeticExpression(Expression::Kind::kAdd, - new ConstantExpression(1), - new ConstantExpression(1)))); + yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression( + Expression::Kind::kAdd, new ConstantExpression(1), new ConstantExpression(1)))); yieldColumns->addColumn(new YieldColumn( new TagPropertyExpression(new std::string("person"), new std::string("name")))); yieldColumns->addColumn(new YieldColumn( @@ -330,6 +523,18 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { PlanNode::Kind::kStart, })); } + { + // with multi-tags + const std::string query = "FETCH PROP ON person \"1\" YIELD person.name AS name" + " | FETCH PROP ON person, book $-.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;" @@ -342,6 +547,18 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { PlanNode::Kind::kStart, })); } + { + // with multi-tags + const std::string query = "$a = FETCH PROP ON person \"1\" YIELD person.name AS name;" + "FETCH PROP ON book,person $a.name"; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } // with project // pipe @@ -357,6 +574,20 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { PlanNode::Kind::kStart, })); } + { + // with multi-tags + const std::string query = + "FETCH PROP ON person \"1\" YIELD person.name + 1 AS name" + " | FETCH PROP ON person,book $-.name YIELD person.name + 1, book.name"; + 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;" @@ -370,6 +601,20 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { PlanNode::Kind::kStart, })); } + { + // with multi-tags + const std::string query = + "$a = FETCH PROP ON person \"1\" YIELD person.name + 1 AS name;" + "FETCH PROP ON book,person $a.name YIELD person.name + 1, book.name "; + EXPECT_TRUE(checkResult(query, + { + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kProject, + PlanNode::Kind::kGetVertices, + PlanNode::Kind::kStart, + })); + } // on * { const std::string query = "FETCH PROP ON person \"1\", \"2\" YIELD person.name AS name" @@ -407,9 +652,8 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { })); } { - const std::string query = - "$a = FETCH PROP ON * \"1\", \"2\" YIELD person.name AS name;" - "FETCH PROP ON * $a.name"; + const std::string query = "$a = FETCH PROP ON * \"1\", \"2\" YIELD person.name AS name;" + "FETCH PROP ON * $a.name"; EXPECT_TRUE(checkResult(query, { PlanNode::Kind::kGetVertices, @@ -422,14 +666,22 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) { TEST_F(FetchVerticesValidatorTest, FetchVerticesPropFailed) { // mismatched tag - ASSERT_FALSE(validate("FETCH PROP ON tag1 \"1\" YIELD tag2.prop2")); + ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD tag2.prop2")); + ASSERT_FALSE(validate("FETCH PROP ON person, book \"1\" YIELD tag3.prop3")); + + // unused tag + ASSERT_FALSE(validate("FETCH PROP ON person, book \"1\" YIELD person.name")); // not exist tag ASSERT_FALSE(validate("FETCH PROP ON not_exist_tag \"1\" YIELD not_exist_tag.prop1")); + ASSERT_FALSE(validate("FETCH PROP ON person, not_exist_tag \"1\"" + " YIELD not_exist_tag.prop1, person.name")); ASSERT_FALSE(validate("FETCH PROP ON * \"1\" YIELD not_exist_tag.prop1")); // not exist property ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person.not_exist_property")); + ASSERT_FALSE(validate("FETCH PROP ON person, book \"1\"" + " YIELD person.not_exist_property, book.not_exist_property")); ASSERT_FALSE(validate("FETCH PROP ON * \"1\" YIELD person.not_exist_property")); // invalid yield expression diff --git a/src/validator/test/MockSchemaManager.cpp b/src/validator/test/MockSchemaManager.cpp index 283721cb7..fa3e1d42e 100644 --- a/src/validator/test/MockSchemaManager.cpp +++ b/src/validator/test/MockSchemaManager.cpp @@ -22,6 +22,8 @@ void MockSchemaManager::init() { edgeIdNames_.emplace(3, "like"); edgeNameIds_.emplace("serve", 4); edgeIdNames_.emplace(4, "serve"); + tagNameIds_.emplace("book", 5); + tagIdNames_.emplace(5, "book"); Tags tagSchemas; // person {name : string, age : int8} @@ -29,6 +31,10 @@ void MockSchemaManager::init() { personSchema->addField("name", meta::cpp2::PropertyType::STRING); personSchema->addField("age", meta::cpp2::PropertyType::INT8); tagSchemas.emplace(2, personSchema); + // book {name : string} + std::shared_ptr bookSchema(new meta::NebulaSchemaProvider(0)); + bookSchema->addField("name", meta::cpp2::PropertyType::STRING); + tagSchemas.emplace(5, bookSchema); tagSchemas_.emplace(1, std::move(tagSchemas)); diff --git a/tests/query/v1/test_fetch_vertex.py b/tests/query/v1/test_fetch_vertex.py index a74f6402c..42ec75552 100644 --- a/tests/query/v1/test_fetch_vertex.py +++ b/tests/query/v1/test_fetch_vertex.py @@ -220,22 +220,24 @@ def test_fetch_vertex_get_all(self): # multi vertices query = 'FETCH PROP ON * "Tim Duncan", "Boris Diaw"' resp = self.execute_query(query) - expect_column_names = ['_vid', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] expect_result = [['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"], ['Boris Diaw', 'Boris Diaw', 36, T_EMPTY, T_EMPTY, T_EMPTY]] self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) self.check_out_of_order_result(resp, expect_result) # from input query = '''GO FROM "Boris Diaw" over like YIELD like._dst as id | FETCH PROP ON * $-.id''' resp = self.execute_query(query) - expect_column_names = ['_vid', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] expect_result = [ ['Tony Parker', 'Tony Parker', 36, T_EMPTY, T_EMPTY, T_EMPTY], ['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"] ] self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) self.check_out_of_order_result(resp, expect_result) # from var @@ -357,6 +359,146 @@ def test_fetch_vertex_get_all_with_yield_invalid(self): resp = self.execute_query(query) self.check_resp_failed(resp) + def test_fetch_vertex_on_multi_tags(self): + query = 'FETCH PROP ON player, team "Boris Diaw"' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name'] + expect_result = [['Boris Diaw', 'Boris Diaw', 36, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # multi vertices + query = 'FETCH PROP ON player, bachelor "Tim Duncan", "Boris Diaw"' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'bachelor.name', 'bachelor.speciality'] + expect_result = [['Tim Duncan', 'Tim Duncan', 42, "Tim Duncan", "psychology"], + ['Boris Diaw', 'Boris Diaw', 36, T_EMPTY, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # from input + query = '''GO FROM "Boris Diaw" over like YIELD like._dst as id + | FETCH PROP ON player, bachelor $-.id''' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'bachelor.name', 'bachelor.speciality'] + expect_result = [ + ['Tony Parker', 'Tony Parker', 36, T_EMPTY, T_EMPTY], + ['Tim Duncan', 'Tim Duncan', 42, "Tim Duncan", "psychology"] + ] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # from var + query = '''$a = GO FROM "Boris Diaw" over like YIELD like._dst as id + | FETCH PROP ON palyer, bachelor * $a.id''' + self.check_resp_succeeded(resp) + self.check_out_of_order_result(resp, expect_result) + + def test_fetch_vertex_on_multi_tags_with_yield(self): + query = 'FETCH PROP ON team, player, bachelor "Boris Diaw" YIELD player.name, player.age, team.name, bachelor.name, bachelor.speciality' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_result = [['Boris Diaw', 'Boris Diaw', 36, T_EMPTY, T_EMPTY, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = 'FETCH PROP ON player, team, bachelor "Boris Diaw" YIELD player.age, team.name, bachelor.speciality' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.age', 'team.name', 'bachelor.speciality'] + expect_result = [['Boris Diaw', 36, T_EMPTY, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = 'FETCH PROP ON team, player, bachelor "Tim Duncan" YIELD player.name, player.age, team.name, bachelor.name, bachelor.speciality' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_result = [['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = 'FETCH PROP ON player, team, bachelor "Tim Duncan" YIELD player.name, team.name, bachelor.name' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'team.name', 'bachelor.name'] + expect_result = [['Tim Duncan', 'Tim Duncan', T_EMPTY, "Tim Duncan"]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # multi vertices + query = 'FETCH PROP ON bachelor, team, player "Tim Duncan", "Boris Diaw" YIELD player.name, player.age, team.name, bachelor.name, bachelor.speciality' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_result = [['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"], + ['Boris Diaw', 'Boris Diaw', 36, T_EMPTY, T_EMPTY, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = 'FETCH PROP ON player, team, bachelor "Tim Duncan", "Boris Diaw" YIELD player.age, team.name, bachelor.name' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.age', 'team.name', 'bachelor.name'] + expect_result = [['Tim Duncan', 42, T_EMPTY, "Tim Duncan"], + ['Boris Diaw', 36, T_EMPTY, T_EMPTY]] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # from input + query = '''GO FROM "Boris Diaw" over like YIELD like._dst as id + | FETCH PROP ON player, team, bachelor $-.id YIELD player.name, player.age, team.name, bachelor.name, bachelor.speciality''' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_result = [ + ['Tony Parker', 'Tony Parker', 36, T_EMPTY, T_EMPTY, T_EMPTY], + ['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"] + ] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = '''GO FROM "Boris Diaw" over like YIELD like._dst as id + | FETCH PROP ON player, team, bachelor $-.id YIELD player.age, team.name, bachelor.speciality''' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.age', 'team.name', 'bachelor.speciality'] + expect_result = [ + ['Tony Parker', 36, T_EMPTY, T_EMPTY], + ['Tim Duncan', 42, T_EMPTY, "psychology"] + ] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + # from var + query = '''$a = GO FROM "Boris Diaw" over like YIELD like._dst as id; + FETCH PROP ON player, team, bachelor $a.id YIELD player.name, player.age, team.name, bachelor.name, bachelor.speciality''' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.name', 'player.age', 'team.name', 'bachelor.name', 'bachelor.speciality'] + expect_result = [ + ['Tony Parker', 'Tony Parker', 36, T_EMPTY, T_EMPTY, T_EMPTY], + ['Tim Duncan', 'Tim Duncan', 42, T_EMPTY, "Tim Duncan", "psychology"] + ] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + + query = '''$a = GO FROM "Boris Diaw" over like YIELD like._dst as id; + FETCH PROP ON player, team, bachelor $a.id YIELD player.age, team.name, bachelor.speciality''' + resp = self.execute_query(query) + expect_column_names = ['VertexID', 'player.age', 'team.name', 'bachelor.speciality'] + expect_result = [ + ['Tony Parker', 36, T_EMPTY, T_EMPTY], + ['Tim Duncan', 42, T_EMPTY, "psychology"] + ] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expect_column_names) + self.check_out_of_order_result(resp, expect_result) + def test_fetch_vertex_duplicate_column_names(self): query = 'FETCH PROP ON player "Boris Diaw" YIELD player.name, player.name' resp = self.execute_query(query)