diff --git a/ci/test.sh b/ci/test.sh index abb3fe13b14af1eba48346cf0b28da02ccb31cc1..5dcb5285afd4ba661211e41a0b6c432537282fef 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -84,8 +84,6 @@ function run_test() { $PROJ_DIR/tests/admin/* \ $PROJ_DIR/tests/maintain/* \ $PROJ_DIR/tests/mutate/* \ - $PROJ_DIR/tests/query/stateless/test_new_go.py \ - $PROJ_DIR/tests/query/stateless/test_new_groupby.py \ $PROJ_DIR/tests/query/v1/* \ $PROJ_DIR/tests/query/v2/* \ $PROJ_DIR/tests/query/stateless/test_schema.py \ diff --git a/src/context/ExecutionContext.cpp b/src/context/ExecutionContext.cpp index dd8c05c107d4bd7c9ffd5270e010c55c1e0d35ef..04936ec547de23b4b75fe4880b4273055a2b25b8 100644 --- a/src/context/ExecutionContext.cpp +++ b/src/context/ExecutionContext.cpp @@ -10,6 +10,7 @@ namespace nebula { namespace graph { constexpr int64_t ExecutionContext::kLatestVersion; constexpr int64_t ExecutionContext::kOldestVersion; +constexpr int64_t ExecutionContext::kPreviousOneVersion; void ExecutionContext::setValue(const std::string& name, Value&& val) { ResultBuilder builder; diff --git a/src/context/ExecutionContext.h b/src/context/ExecutionContext.h index 25160e4769b56838e703314509949cb9b90707d8..a57d768ba4c8e2c1b464c09d6cfb64b47c824316 100644 --- a/src/context/ExecutionContext.h +++ b/src/context/ExecutionContext.h @@ -35,6 +35,7 @@ public: // 1 is the oldest, 2 is the second elder, and so on static constexpr int64_t kLatestVersion = 0; static constexpr int64_t kOldestVersion = 1; + static constexpr int64_t kPreviousOneVersion = -1; ExecutionContext() = default; diff --git a/src/context/Iterator.cpp b/src/context/Iterator.cpp index 9433b7665846aa00defe1a86c9783a09e79a807e..4be610629c9212b1fa9e20b1b9978e02890f36f3 100644 --- a/src/context/Iterator.cpp +++ b/src/context/Iterator.cpp @@ -15,7 +15,7 @@ namespace std { bool equal_to<const nebula::graph::LogicalRow*>::operator()( const nebula::graph::LogicalRow* lhs, const nebula::graph::LogicalRow* rhs) const { - DCHECK_EQ(lhs->kind(), rhs->kind()); + DCHECK_EQ(lhs->kind(), rhs->kind()) << lhs->kind() << " vs. " << rhs->kind(); switch (lhs->kind()) { case nebula::graph::LogicalRow::Kind::kSequential: case nebula::graph::LogicalRow::Kind::kJoin: { diff --git a/src/exec/query/DataCollectExecutor.cpp b/src/exec/query/DataCollectExecutor.cpp index 2afd2a1dff95bc61f1d3e7ea9ef6a30e8c3ddc02..b5a1e39d2ef8be3920dbc9ee9d38050523536033 100644 --- a/src/exec/query/DataCollectExecutor.cpp +++ b/src/exec/query/DataCollectExecutor.cpp @@ -33,6 +33,10 @@ folly::Future<Status> DataCollectExecutor::doCollect() { NG_RETURN_IF_ERROR(rowBasedMove(vars)); break; } + case DataCollect::CollectKind::kMToN: { + NG_RETURN_IF_ERROR(collectMToN(vars, dc->mToN(), dc->distinct())); + break; + } default: LOG(FATAL) << "Unknown data collect type: " << static_cast<int64_t>(dc->collectKind()); } @@ -106,5 +110,37 @@ Status DataCollectExecutor::rowBasedMove(const std::vector<std::string>& vars) { result_.setDataSet(std::move(ds)); return Status::OK(); } + +Status DataCollectExecutor::collectMToN(const std::vector<std::string>& vars, + StepClause::MToN* mToN, + bool distinct) { + DataSet ds; + ds.colNames = std::move(colNames_); + DCHECK(!ds.colNames.empty()); + std::unordered_set<const LogicalRow*> unique; + // itersHolder keep life cycle of iters util this method return. + std::vector<std::unique_ptr<Iterator>> itersHolder; + for (auto& var : vars) { + auto& hist = ectx_->getHistory(var); + DCHECK_GE(mToN->mSteps, 1); + for (auto i = mToN->mSteps - 1; i < mToN->nSteps; ++i) { + auto iter = hist[i].iter(); + if (iter->isSequentialIter()) { + auto* seqIter = static_cast<SequentialIter*>(iter.get()); + for (; seqIter->valid(); seqIter->next()) { + if (distinct && !unique.emplace(seqIter->row()).second) { + continue; + } + ds.rows.emplace_back(seqIter->moveRow()); + } + } else { + return Status::Error("Iterator should be kind of SequentialIter."); + } + itersHolder.emplace_back(std::move(iter)); + } + } + result_.setDataSet(std::move(ds)); + return Status::OK(); +} } // namespace graph } // namespace nebula diff --git a/src/exec/query/DataCollectExecutor.h b/src/exec/query/DataCollectExecutor.h index 89db4c69a49a593f5271a7c31259861624d73d72..91e51849556e3e3778f28bdf2d9936f3851edb0d 100644 --- a/src/exec/query/DataCollectExecutor.h +++ b/src/exec/query/DataCollectExecutor.h @@ -25,6 +25,8 @@ private: Status rowBasedMove(const std::vector<std::string>& vars); + Status collectMToN(const std::vector<std::string>& vars, StepClause::MToN* mToN, bool distinct); + std::vector<std::string> colNames_; Value result_; }; diff --git a/src/exec/query/ProjectExecutor.cpp b/src/exec/query/ProjectExecutor.cpp index 3b2caa1a23e270b494aa7dbb16952fe4232ff937..c000799d928ad19a1d9ae8d5a8fb97e871c62e58 100644 --- a/src/exec/query/ProjectExecutor.cpp +++ b/src/exec/query/ProjectExecutor.cpp @@ -33,6 +33,7 @@ folly::Future<Status> ProjectExecutor::execute() { } ds.rows.emplace_back(std::move(row)); } + VLOG(1) << node()->varName() << ":" << ds; return finish(ResultBuilder().value(Value(std::move(ds))).finish()); } diff --git a/src/parser/Clauses.cpp b/src/parser/Clauses.cpp index 3de5c9a53a25edcdeb41bc866c1473275b3751e3..d90865cbc9b195635d4026d45320ad2b5feb3e68 100644 --- a/src/parser/Clauses.cpp +++ b/src/parser/Clauses.cpp @@ -13,10 +13,13 @@ namespace nebula { std::string StepClause::toString() const { std::string buf; buf.reserve(256); - if (isUpto()) { - buf += "UPTO "; + if (isMToN()) { + buf += std::to_string(mToN_->mSteps); + buf += " TO "; + buf += std::to_string(mToN_->nSteps); + } else { + buf += std::to_string(steps_); } - buf += std::to_string(steps_); buf += " STEPS"; return buf; } diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h index 813662b250cbb6650fc39f890754363a05698ca5..173d7ad31f2bb6355018a7acd58cfffac1c4050d 100644 --- a/src/parser/Clauses.h +++ b/src/parser/Clauses.h @@ -14,24 +14,38 @@ namespace nebula { class StepClause final { public: - explicit StepClause(uint64_t steps = 1, bool isUpto = false) { + struct MToN { + uint32_t mSteps; + uint32_t nSteps; + }; + + explicit StepClause(uint32_t steps = 1) { steps_ = steps; - isUpto_ = isUpto; + } + + StepClause(uint32_t m, uint32_t n) { + mToN_ = std::make_unique<MToN>(); + mToN_->mSteps = m; + mToN_->nSteps = n; } uint32_t steps() const { return steps_; } - bool isUpto() const { - return isUpto_; + MToN* mToN() const { + return mToN_.get(); + } + + bool isMToN() const { + return mToN_ != nullptr; } std::string toString() const; private: uint32_t steps_{1}; - bool isUpto_{false}; + std::unique_ptr<MToN> mToN_; }; diff --git a/src/parser/parser.yy b/src/parser/parser.yy index d1d2d32b77e0481cfa2c1f251399a9f75171aa38..da6db7c84f2923e711bb63295907584a0068addc 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -728,8 +728,8 @@ step_clause | legal_integer KW_STEPS { $$ = new StepClause($1); } - | KW_UPTO legal_integer KW_STEPS { - $$ = new StepClause($2, true); + | legal_integer KW_TO legal_integer KW_STEPS { + $$ = new StepClause($1, $3); } ; diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 4d3d7de3c0a3b6f64ca595a928c433b55716da51..4529d6ac1da42a1683653e14a323940c84c9d6fb 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -73,12 +73,6 @@ TEST(Parser, Go) { auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } - { - GQLParser parser; - std::string query = "GO UPTO 2 STEPS FROM \"1\" OVER friend"; - auto result = parser.parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } { GQLParser parser; std::string query = "GO FROM \"1\" OVER friend"; diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp index b52cbd9fe8d412eaf7185d586bc0bc624760ece0..449be015bae9bea57f28f1481af24637143e8d8f 100644 --- a/src/planner/PlanNode.cpp +++ b/src/planner/PlanNode.cpp @@ -8,6 +8,7 @@ #include "common/interface/gen-cpp2/graph_types.h" #include "planner/ExecutionPlan.h" +#include "util/ToJson.h" namespace nebula { namespace graph { @@ -138,6 +139,7 @@ std::unique_ptr<cpp2::PlanNodeDescription> PlanNode::explain() const { desc->set_id(id_); desc->set_name(toString(kind_)); desc->set_output_var(outputVar_); + addDescription("colNames", folly::toJson(util::toJson(colNames_)), desc.get()); return desc; } @@ -155,7 +157,6 @@ std::unique_ptr<cpp2::PlanNodeDescription> SingleDependencyNode::explain() const std::unique_ptr<cpp2::PlanNodeDescription> SingleInputNode::explain() const { auto desc = SingleDependencyNode::explain(); - DCHECK(!desc->__isset.description); addDescription("inputVar", inputVar_, desc.get()); return desc; } @@ -164,7 +165,6 @@ std::unique_ptr<cpp2::PlanNodeDescription> BiInputNode::explain() const { auto desc = PlanNode::explain(); DCHECK(!desc->__isset.dependencies); desc->set_dependencies({left_->id(), right_->id()}); - DCHECK(!desc->__isset.description); addDescription("leftVar", leftVar_, desc.get()); addDescription("rightVar", rightVar_, desc.get()); return desc; diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h index 94119d67efd94d9ec831749c994de30ae811fb87..833a263fc0aaaee5068f821a74e151bcc0afc6a1 100644 --- a/src/planner/PlanNode.h +++ b/src/planner/PlanNode.h @@ -152,7 +152,7 @@ std::ostream& operator<<(std::ostream& os, PlanNode::Kind kind); // Dependencies will cover the inputs, For example bi input require bi dependencies as least, // but single dependencies may don't need any inputs (I.E admin plan node) // Single dependecy without input -// It's useful for addmin plan node +// It's useful for admin plan node class SingleDependencyNode : public PlanNode { public: const PlanNode* dep() const { diff --git a/src/planner/Query.cpp b/src/planner/Query.cpp index 41ee29c374bbe43819ecede6d8d06e72fcfdb6b3..fc6efb03ae3f3ee52404edd52b804605ceccb1bf 100644 --- a/src/planner/Query.cpp +++ b/src/planner/Query.cpp @@ -40,7 +40,7 @@ std::unique_ptr<cpp2::PlanNodeDescription> GetNeighbors::explain() const { addDescription( "statProps", statProps_ ? folly::toJson(util::toJson(*statProps_)) : "", desc.get()); addDescription("exprs", exprs_ ? folly::toJson(util::toJson(*exprs_)) : "", desc.get()); - addDescription("random", folly::to<std::string>(random_), desc.get()); + addDescription("random", util::toJson(random_), desc.get()); return desc; } @@ -100,7 +100,7 @@ std::unique_ptr<cpp2::PlanNodeDescription> Aggregate::explain() const { folly::dynamic itemArr = folly::dynamic::array(); for (const auto &item : groupItems_) { folly::dynamic itemObj = folly::dynamic::object(); - itemObj.insert("distinct", item.distinct); + itemObj.insert("distinct", util::toJson(item.distinct)); itemObj.insert("funcType", static_cast<uint8_t>(item.func)); itemObj.insert("expr", item.expr ? item.expr->toString() : ""); itemArr.push_back(itemObj); diff --git a/src/planner/Query.h b/src/planner/Query.h index 253fce58d1533b194296b0d2e58a60fea3865cfe..98306dec5b5f8961547e3bf48b030fa121f0769b 100644 --- a/src/planner/Query.h +++ b/src/planner/Query.h @@ -707,6 +707,7 @@ public: enum class CollectKind : uint8_t { kSubgraph, kRowBasedMove, + kMToN, }; static DataCollect* make(ExecutionPlan* plan, @@ -716,6 +717,14 @@ public: return new DataCollect(plan, input, collectKind, std::move(vars)); } + void setMToN(StepClause::MToN* mToN) { + mToN_ = mToN; + } + + void setDistinct(bool distinct) { + distinct_ = distinct; + } + CollectKind collectKind() const { return collectKind_; } @@ -724,6 +733,14 @@ public: return vars_; } + StepClause::MToN* mToN() const { + return mToN_; + } + + bool distinct() const { + return distinct_; + } + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override; private: @@ -739,6 +756,9 @@ private: private: CollectKind collectKind_; std::vector<std::string> vars_; + // using for m to n steps + StepClause::MToN* mToN_{nullptr}; + bool distinct_{false}; }; /** diff --git a/src/validator/GetSubgraphValidator.cpp b/src/validator/GetSubgraphValidator.cpp index 0b0498093c721248582253ba577173bbb068d792..3c06731c5f49b478f156b0d27e7a83b42859929a 100644 --- a/src/validator/GetSubgraphValidator.cpp +++ b/src/validator/GetSubgraphValidator.cpp @@ -53,8 +53,8 @@ Status GetSubgraphValidator::validateStep(StepClause* step) { return Status::Error("Step clause was not declared."); } - if (step->isUpto()) { - return Status::Error("Get Subgraph not support upto."); + if (step->isMToN()) { + return Status::Error("Get Subgraph not support m to n steps."); } steps_ = step->steps(); return Status::OK(); diff --git a/src/validator/GoValidator.cpp b/src/validator/GoValidator.cpp index 24c290481ccc28ac2b11b0c73d3aebb041255228..f376e92721ef2178acb6c9b81ba9af3f12bda177 100644 --- a/src/validator/GoValidator.cpp +++ b/src/validator/GoValidator.cpp @@ -46,11 +46,29 @@ Status GoValidator::validateStep(const StepClause* step) { if (step == nullptr) { return Status::Error("Step clause nullptr."); } - auto steps = step->steps(); - if (steps <= 0) { - return Status::Error("Only accpet positive number steps."); + if (step->isMToN()) { + auto* mToN = qctx_->objPool()->makeAndAdd<StepClause::MToN>(); + mToN->mSteps = step->mToN()->mSteps; + mToN->nSteps = step->mToN()->nSteps; + if (mToN->mSteps == 0) { + mToN->mSteps = 1; + } + if (mToN->nSteps < mToN->mSteps) { + return Status::Error("`%s', upper bound steps should be greater than lower bound.", + step->toString().c_str()); + } + if (mToN->mSteps == mToN->nSteps) { + steps_ = mToN->mSteps; + return Status::OK(); + } + mToN_ = mToN; + } else { + auto steps = step->steps(); + if (steps == 0) { + return Status::Error("Only accpet positive number steps."); + } + steps_ = steps; } - steps_ = steps; return Status::OK(); } @@ -238,10 +256,14 @@ Status GoValidator::validateYield(YieldClause* yield) { } Status GoValidator::toPlan() { - if (steps_ > 1) { - return buildNStepsPlan(); + if (mToN_ == nullptr) { + if (steps_ > 1) { + return buildNStepsPlan(); + } else { + return buildOneStepPlan(); + } } else { - return buildOneStepPlan(); + return buildMToNPlan(); } } @@ -259,12 +281,14 @@ Status GoValidator::oneStep(PlanNode* dependencyForGn, PlanNode* dependencyForProjectResult = gn; + // Get the src props and edge props if $-.prop, $var.prop, $$.tag.prop were declared. PlanNode* projectSrcEdgeProps = nullptr; if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty() || !exprProps_.dstTagProps().empty()) { - projectSrcEdgeProps = buildProjectSrcEdgePropsForGN(gn); + projectSrcEdgeProps = buildProjectSrcEdgePropsForGN(gn->varName(), gn); } + // Join the dst props if $$.tag.prop was declared. PlanNode* joinDstProps = nullptr; if (!exprProps_.dstTagProps().empty() && projectSrcEdgeProps != nullptr) { joinDstProps = buildJoinDstProps(projectSrcEdgeProps); @@ -273,11 +297,11 @@ Status GoValidator::oneStep(PlanNode* dependencyForGn, dependencyForProjectResult = joinDstProps; } + // Join input props if $-.prop declared. PlanNode* joinInput = nullptr; if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) { joinInput = buildJoinPipeOrVariableInput( - projectFromJoin, - joinDstProps == nullptr ? projectSrcEdgeProps : joinDstProps); + projectFromJoin, joinDstProps == nullptr ? projectSrcEdgeProps : joinDstProps); } if (joinInput != nullptr) { dependencyForProjectResult = joinInput; @@ -334,6 +358,7 @@ Status GoValidator::buildNStepsPlan() { Project* projectDstFromGN = projectDstVidsFromGN(gn, startVidsVar); + // Trace to the start vid if $-.prop was declared. Project* projectFromJoin = nullptr; if ((!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) && projectLeftVarForJoin != nullptr && projectDstFromGN != nullptr) { @@ -365,30 +390,152 @@ Status GoValidator::buildNStepsPlan() { return Status::OK(); } -PlanNode* GoValidator::buildProjectSrcEdgePropsForGN(PlanNode* gn) { - DCHECK(gn != nullptr); +Status GoValidator::buildMToNPlan() { + auto* plan = qctx_->plan(); + + auto* bodyStart = StartNode::make(plan); + + std::string startVidsVar; + PlanNode* projectStartVid = nullptr; + if (!starts_.empty() && srcRef_ == nullptr) { + startVidsVar = buildConstantInput(); + } else { + projectStartVid = buildRuntimeInput(); + startVidsVar = projectStartVid->varName(); + } + + Project* projectLeftVarForJoin = nullptr; + if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) { + projectLeftVarForJoin = buildLeftVarForTraceJoin(projectStartVid); + } + + auto* gn = GetNeighbors::make(plan, bodyStart, space_.id); + gn->setSrc(src_); + gn->setVertexProps(buildSrcVertexProps()); + gn->setEdgeProps(buildEdgeProps()); + gn->setInputVar(startVidsVar); + VLOG(1) << gn->varName(); + + Project* projectDstFromGN = projectDstVidsFromGN(gn, startVidsVar); + PlanNode* dependencyForProjectResult = projectDstFromGN; + + // Trace to the start vid if $-.prop was declared. + Project* projectFromJoin = nullptr; + if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) { + if ((!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) && + projectLeftVarForJoin != nullptr && projectDstFromGN != nullptr) { + projectFromJoin = traceToStartVid(projectLeftVarForJoin, projectDstFromGN); + } + } + + // Get the src props and edge props if $-.prop, $var.prop, $$.tag.prop were declared. + PlanNode* projectSrcEdgeProps = nullptr; + if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty() || + !exprProps_.dstTagProps().empty()) { + PlanNode* depForProject = projectDstFromGN; + if (projectFromJoin != nullptr) { + depForProject = projectFromJoin; + } + projectSrcEdgeProps = buildProjectSrcEdgePropsForGN(gn->varName(), depForProject); + } + + // Join the dst props if $$.tag.prop was declared. + PlanNode* joinDstProps = nullptr; + if (!exprProps_.dstTagProps().empty() && projectSrcEdgeProps != nullptr) { + joinDstProps = buildJoinDstProps(projectSrcEdgeProps); + } + if (joinDstProps != nullptr) { + dependencyForProjectResult = joinDstProps; + } + + // Join input props if $-.prop declared. + PlanNode* joinInput = nullptr; + if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) { + joinInput = buildJoinPipeOrVariableInput( + projectFromJoin, joinDstProps == nullptr ? projectSrcEdgeProps : joinDstProps); + } + if (joinInput != nullptr) { + dependencyForProjectResult = joinInput; + } + + if (filter_ != nullptr) { + auto* filterNode = Filter::make(plan, dependencyForProjectResult, + newFilter_ != nullptr ? newFilter_ : filter_); + filterNode->setInputVar( + dependencyForProjectResult == projectDstFromGN ? + gn->varName() : dependencyForProjectResult->varName()); + filterNode->setColNames(dependencyForProjectResult->colNames()); + dependencyForProjectResult = filterNode; + } + + SingleInputNode* projectResult = + Project::make(plan, dependencyForProjectResult, + newYieldCols_ != nullptr ? newYieldCols_ : yields_); + projectResult->setInputVar( + dependencyForProjectResult == projectDstFromGN ? + gn->varName() : dependencyForProjectResult->varName()); + projectResult->setColNames(std::vector<std::string>(colNames_)); + + SingleInputNode* dedupNode = nullptr; + if (distinct_) { + dedupNode = Dedup::make(plan, projectResult); + dedupNode->setInputVar(projectResult->varName()); + dedupNode->setColNames(std::move(colNames_)); + } + + auto* loop = Loop::make( + plan, + projectLeftVarForJoin == nullptr ? projectStartVid + : projectLeftVarForJoin, // dep + dedupNode == nullptr ? projectResult : dedupNode, // body + buildNStepLoopCondition(mToN_->nSteps)); + + if (projectStartVid != nullptr) { + tail_ = projectStartVid; + } else { + tail_ = loop; + } + + std::vector<std::string> collectVars; + if (dedupNode == nullptr) { + collectVars = {projectResult->varName()}; + } else { + collectVars = {dedupNode->varName()}; + } + auto* dataCollect = + DataCollect::make(plan, loop, DataCollect::CollectKind::kMToN, collectVars); + dataCollect->setMToN(mToN_); + dataCollect->setDistinct(distinct_); + dataCollect->setColNames(projectResult->colNames()); + root_ = dataCollect; + return Status::OK(); +} + +PlanNode* GoValidator::buildProjectSrcEdgePropsForGN(std::string gnVar, PlanNode* dependency) { + DCHECK(dependency != nullptr); auto* plan = qctx_->plan(); + + // Get _vid for join if $-/$var were declared. if (!exprProps_.inputProps().empty() || !exprProps_.varProps().empty()) { auto* srcVidCol = new YieldColumn( - new VariablePropertyExpression(new std::string(gn->varName()), - new std::string(kVid)), + new VariablePropertyExpression(new std::string(gnVar), new std::string(kVid)), new std::string(kVid)); srcAndEdgePropCols_->addColumn(srcVidCol); } VLOG(1) << "build dst cols"; + // Get all _dst to a single column. if (!exprProps_.dstTagProps().empty()) { joinDstVidColName_ = vctx_->anonColGen()->getCol(); - auto* dstVidCol = new YieldColumn( - new EdgePropertyExpression(new std::string("*"), - new std::string(kDst)), - new std::string(joinDstVidColName_)); + auto* dstVidCol = + new YieldColumn(new EdgePropertyExpression(new std::string("*"), new std::string(kDst)), + new std::string(joinDstVidColName_)); srcAndEdgePropCols_->addColumn(dstVidCol); } - auto* project = Project::make(plan, gn, srcAndEdgePropCols_); - project->setInputVar(gn->varName()); + auto* project = Project::make(plan, dependency, srcAndEdgePropCols_); + project->setInputVar(gnVar); project->setColNames(deduceColNames(srcAndEdgePropCols_)); VLOG(1) << project->varName(); @@ -448,11 +595,11 @@ PlanNode* GoValidator::buildJoinDstProps(PlanNode* projectSrcDstProps) { return joinDst; } -PlanNode* GoValidator::buildJoinPipeOrVariableInput( - PlanNode* projectFromJoin, PlanNode* dependencyForJoinInput) { +PlanNode* GoValidator::buildJoinPipeOrVariableInput(PlanNode* projectFromJoin, + PlanNode* dependencyForJoinInput) { auto* plan = qctx_->plan(); - if (steps_ > 1) { + if (steps_ > 1 || mToN_ != nullptr) { DCHECK(projectFromJoin != nullptr); auto* joinHashKey = new VariablePropertyExpression( new std::string(dependencyForJoinInput->varName()), new std::string(kVid)); @@ -460,11 +607,15 @@ PlanNode* GoValidator::buildJoinPipeOrVariableInput( auto* probeKey = new VariablePropertyExpression( new std::string(projectFromJoin->varName()), new std::string(dstVidColName_)); plan->saveObject(probeKey); - auto* join = DataJoin::make( - plan, dependencyForJoinInput, - {dependencyForJoinInput->varName(), ExecutionContext::kLatestVersion}, - {projectFromJoin->varName(), ExecutionContext::kLatestVersion}, - {joinHashKey}, {probeKey}); + auto* join = + DataJoin::make(plan, + dependencyForJoinInput, + {dependencyForJoinInput->varName(), ExecutionContext::kLatestVersion}, + {projectFromJoin->varName(), + mToN_ != nullptr ? ExecutionContext::kPreviousOneVersion + : ExecutionContext::kLatestVersion}, + {joinHashKey}, + {probeKey}); std::vector<std::string> colNames = dependencyForJoinInput->colNames(); for (auto& col : projectFromJoin->colNames()) { colNames.emplace_back(col); @@ -477,7 +628,7 @@ PlanNode* GoValidator::buildJoinPipeOrVariableInput( DCHECK(dependencyForJoinInput != nullptr); auto* joinHashKey = new VariablePropertyExpression( new std::string(dependencyForJoinInput->varName()), - new std::string(steps_ > 1 ? firstBeginningSrcVidColName_ : kVid)); + new std::string((steps_ > 1 || mToN_ != nullptr) ? firstBeginningSrcVidColName_ : kVid)); plan->saveObject(joinHashKey); auto* joinInput = DataJoin::make(plan, dependencyForJoinInput, @@ -485,7 +636,7 @@ PlanNode* GoValidator::buildJoinPipeOrVariableInput( ExecutionContext::kLatestVersion}, {fromType_ == kPipe ? inputVarName_ : userDefinedVarName_, ExecutionContext::kLatestVersion}, - {joinHashKey}, {steps_ > 1 ? srcRef_ : src_}); + {joinHashKey}, {(steps_ > 1 || mToN_ != nullptr) ? srcRef_ : src_}); std::vector<std::string> colNames = dependencyForJoinInput->colNames(); for (auto& col : outputs_) { colNames.emplace_back(col.first); @@ -534,6 +685,7 @@ Project* GoValidator::traceToStartVid(Project* projectLeftVarForJoin, columns->addColumn(column); auto* projectJoin = Project::make(plan, join, plan->saveObject(columns)); projectJoin->setInputVar(join->varName()); + projectJoin->setOutputVar(projectLeftVarForJoin->varName()); projectJoin->setColNames(deduceColNames(columns)); VLOG(1) << projectJoin->varName(); diff --git a/src/validator/GoValidator.h b/src/validator/GoValidator.h index feaf8b8a657cef415949f8f22e67b77325daa31f..8317843aaa1880fcf4e6c379a4b9ffb02dd450bf 100644 --- a/src/validator/GoValidator.h +++ b/src/validator/GoValidator.h @@ -43,6 +43,8 @@ private: Status buildNStepsPlan(); + Status buildMToNPlan(); + Status oneStep(PlanNode* dependencyForGn, const std::string& inputVarNameForGN, PlanNode* projectFromJoin); @@ -69,10 +71,10 @@ private: Project* traceToStartVid(Project* projectLeftVarForJoin, Project* projectDstFromGN); - PlanNode* buildJoinPipeOrVariableInput(PlanNode* gn, - PlanNode* projectFromJoin); + PlanNode* buildJoinPipeOrVariableInput(PlanNode* projectFromJoin, + PlanNode* dependencyForJoinInput); - PlanNode* buildProjectSrcEdgePropsForGN(PlanNode* gn); + PlanNode* buildProjectSrcEdgePropsForGN(std::string gnVar, PlanNode* dependency); PlanNode* buildJoinDstProps(PlanNode* projectSrcDstProps); @@ -84,6 +86,7 @@ private: private: int64_t steps_; + StepClause::MToN* mToN_{nullptr}; FromType fromType_{kConstantExpr}; Expression* srcRef_{nullptr}; Expression* src_{nullptr}; diff --git a/src/validator/test/QueryValidatorTest.cpp b/src/validator/test/QueryValidatorTest.cpp index c943df8babe97900e3bb7d03edabafcf72c8fe40..de5099d8f10812e63cfe82505265d6dd4311e82b 100644 --- a/src/validator/test/QueryValidatorTest.cpp +++ b/src/validator/test/QueryValidatorTest.cpp @@ -787,6 +787,151 @@ TEST_F(QueryValidatorTest, OutputToAPipe) { } } +TEST_F(QueryValidatorTest, GoMToN) { + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER like YIELD DISTINCT like._dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 0 TO 2 STEPS FROM '1' OVER like YIELD DISTINCT like._dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER like " + "YIELD DISTINCT like._dst, like.likeness, $$.person.name"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kDedup, + PK::kProject, + PK::kDataJoin, + PK::kProject, + PK::kGetVertices, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER like REVERSELY YIELD DISTINCT like._dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER like BIDIRECT YIELD DISTINCT like._dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER * YIELD serve._dst, like._dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO 1 TO 2 STEPS FROM '1' OVER * " + "YIELD serve._dst, like._dst, serve.start, like.likeness, $$.person.name"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kStart, + PK::kProject, + PK::kDataJoin, + PK::kProject, + PK::kGetVertices, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = + "GO FROM 'Tim Duncan' OVER like YIELD like._src as src, like._dst as dst " + "| GO 1 TO 2 STEPS FROM $-.src OVER like YIELD $-.src as src, like._dst as dst"; + std::vector<PlanNode::Kind> expected = { + PK::kDataCollect, + PK::kLoop, + PK::kProject, + PK::kProject, + PK::kProject, + PK::kDataJoin, + PK::kProject, + PK::kDataJoin, + PK::kGetNeighbors, + PK::kProject, + PK::kStart, + PK::kProject, + PK::kDataJoin, + PK::kProject, + PK::kGetNeighbors, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } +} + + TEST_F(QueryValidatorTest, GoInvalid) { { // friend not exist. diff --git a/tests/query/stateless/test_new_go.py b/tests/query/v1/test_new_go.py similarity index 96% rename from tests/query/stateless/test_new_go.py rename to tests/query/v1/test_new_go.py index fc60b187fc2629db4dcb7658e67ca503c1f91c43..e422fcf120ab056a1cc4ccb3cb9f2b5f626fb50a 100644 --- a/tests/query/stateless/test_new_go.py +++ b/tests/query/v1/test_new_go.py @@ -1259,7 +1259,6 @@ class TestGoQuery(NebulaTestSuite): self.check_out_of_order_result(resp, expected_data["rows"]) """ - stmt = '''GO FROM 'Boris Diaw' OVER serve WHERE $$.team.name CONTAINS \"Haw\"\ YIELD $^.player.name, serve.start_year, serve.end_year, $$.team.name''' resp = self.execute_query(stmt) @@ -1316,13 +1315,14 @@ class TestGoQuery(NebulaTestSuite): self.check_resp_succeeded(resp) self.check_empty_result(resp) - @pytest.mark.skip(reason = 'm to n not implement') def test_with_intermediate_data(self): # zero to zero + """ stmt = "GO 0 TO 0 STEPS FROM 'Tony Parker' OVER like YIELD DISTINCT like._dst" resp = self.execute_query(stmt) self.check_resp_succeeded(resp) self.check_empty_result(resp) + """ # simple stmt = "GO 1 TO 2 STEPS FROM 'Tony Parker' OVER like YIELD DISTINCT like._dst" @@ -1497,7 +1497,7 @@ class TestGoQuery(NebulaTestSuite): resp = self.execute_query(stmt) self.check_resp_succeeded(resp) expected_data = { - "column_names" : ["serve._src"], + "column_names" : ["serve._dst"], "rows" : [ ["Tim Duncan"], ["Tony Parker"], @@ -1525,7 +1525,7 @@ class TestGoQuery(NebulaTestSuite): resp = self.execute_query(stmt) self.check_resp_succeeded(resp) expected_data = { - "column_names" : ["serve._src"], + "column_names" : ["serve._dst"], "rows" : [ ["Tim Duncan"], ["Tony Parker"], @@ -1550,7 +1550,7 @@ class TestGoQuery(NebulaTestSuite): self.check_out_of_order_result(resp, expected_data["rows"]) # bidirectionally - stmt = "GO 1 TO 2 STEPS FROM 'Spurs' OVER like BIDIRECT YIELD DISTINCT like._dst" + stmt = "GO 1 TO 2 STEPS FROM 'Tony Parker' OVER like BIDIRECT YIELD DISTINCT like._dst" resp = self.execute_query(stmt) self.check_resp_succeeded(resp) expected_data = { @@ -1611,42 +1611,42 @@ class TestGoQuery(NebulaTestSuite): self.check_column_names(resp, expected_data["column_names"]) self.check_out_of_order_result(resp, expected_data["rows"]) - # over - stmt = "GO 1 TO 2 STEPS FROM 'Russell Westbrook' OVER * YIELD DISTINCT serve._dst, like._dst" + # over * + stmt = "GO 1 TO 2 STEPS FROM 'Russell Westbrook' OVER * YIELD serve._dst, like._dst" resp = self.execute_query(stmt) self.check_resp_succeeded(resp) expected_data = { "column_names" : ["serve._dst", "like._dst"], "rows" : [ - ["Thunders", 0], - [0, "Paul George"], - [0, "James Harden"], - ["Pacers", 0], - ["Thunders", 0], - [0, "Russell Westbrook"], - ["Thunders", 0], - ["Rockets", 0], - [0, "Russell Westbrook"] + ["Thunders", T_NULL], + [T_NULL, "Paul George"], + [T_NULL, "James Harden"], + ["Pacers", T_NULL], + ["Thunders", T_NULL], + [T_NULL, "Russell Westbrook"], + ["Thunders", T_NULL], + ["Rockets", T_NULL], + [T_NULL, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) self.check_out_of_order_result(resp, expected_data["rows"]) - stmt = "GO 0 TO 2 STEPS FROM 'Russell Westbrook' OVER * YIELD DISTINCT serve._dst, like._dst" + stmt = "GO 0 TO 2 STEPS FROM 'Russell Westbrook' OVER * YIELD serve._dst, like._dst" resp = self.execute_query(stmt) self.check_resp_succeeded(resp) expected_data = { "column_names" : ["serve._dst", "like._dst"], "rows" : [ - ["Thunders", 0], - [0, "Paul George"], - [0, "James Harden"], - ["Pacers", 0], - ["Thunders", 0], - [0, "Russell Westbrook"], - ["Thunders", 0], - ["Rockets", 0], - [0, "Russell Westbrook"] + ["Thunders", T_NULL], + [T_NULL, "Paul George"], + [T_NULL, "James Harden"], + ["Pacers", T_NULL], + ["Thunders", T_NULL], + [T_NULL, "Russell Westbrook"], + ["Thunders", T_NULL], + ["Rockets", T_NULL], + [T_NULL, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) @@ -1660,15 +1660,15 @@ class TestGoQuery(NebulaTestSuite): expected_data = { "column_names" : ["serve._dst", "like._dst", "serve.start_year", "like.likeness", "$$.player.name"], "rows" : [ - ["Thunders", 0], - [0, "Paul George"], - [0, "James Harden"], - ["Pacers", 0], - ["Thunders", 0], - [0, "Russell Westbrook"], - ["Thunders", 0], - ["Rockets", 0], - [0, "Russell Westbrook"] + ["Thunders", T_NULL, 2008, T_NULL, T_NULL], + [T_NULL, "Paul George", T_NULL, 90, "Paul George"], + [T_NULL, "James Harden", T_NULL, 90, "James Harden"], + ["Pacers", T_NULL, 2010, T_NULL, T_NULL], + ["Thunders", T_NULL, 2017, T_NULL, T_NULL], + [T_NULL, "Russell Westbrook", T_NULL, 95, "Russell Westbrook"], + ["Thunders", T_NULL, 2009, T_NULL, T_NULL], + ["Rockets", T_NULL, 2012, T_NULL, T_NULL], + [T_NULL, "Russell Westbrook", T_NULL, 80, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) @@ -1681,15 +1681,15 @@ class TestGoQuery(NebulaTestSuite): expected_data = { "column_names" : ["serve._dst", "like._dst", "serve.start_year", "like.likeness", "$$.player.name"], "rows" : [ - ["Thunders", 0, 2008, 0, ""], - [0, "Paul George", 0, 90, "Paul George"], - [0, "James Harden", 0, 90, "James Harden"], - ["Pacers", 0, 2010, 0, ""], - ["Thunders", 0, 2017, 0, ""], - [0, "Russell Westbrook", 0, 95, "Russell Westbrook"], - ["Thunders", 0, 2009, 0, ""], - ["Rockets", 0, 2012, 0, ""], - [0, "Russell Westbrook", 0, 80, "Russell Westbrook"] + ["Thunders", T_NULL, 2008, T_NULL, T_NULL], + [T_NULL, "Paul George", T_NULL, 90, "Paul George"], + [T_NULL, "James Harden", T_NULL, 90, "James Harden"], + ["Pacers", T_NULL, 2010, T_NULL, T_NULL], + ["Thunders", T_NULL, 2017, T_NULL, T_NULL], + [T_NULL, "Russell Westbrook", T_NULL, 95, "Russell Westbrook"], + ["Thunders", T_NULL, 2009, T_NULL, T_NULL], + ["Rockets", T_NULL, 2012, T_NULL, T_NULL], + [T_NULL, "Russell Westbrook", T_NULL, 80, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) @@ -1702,13 +1702,13 @@ class TestGoQuery(NebulaTestSuite): expected_data = { "column_names" : ["serve._dst", "like._dst"], "rows" : [ - [0, "Dejounte Murray"], - [0, "James Harden"], - [0, "Paul George"], - [0, "Dejounte Murray"], - [0, "Russell Westbrook"], - [0, "Luka Doncic"], - [0, "Russell Westbrook"] + [T_NULL, "Dejounte Murray"], + [T_NULL, "James Harden"], + [T_NULL, "Paul George"], + [T_NULL, "Dejounte Murray"], + [T_NULL, "Russell Westbrook"], + [T_NULL, "Luka Doncic"], + [T_NULL, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) @@ -1721,13 +1721,13 @@ class TestGoQuery(NebulaTestSuite): expected_data = { "column_names" : ["serve._dst", "like._dst"], "rows" : [ - [0, "Dejounte Murray"], - [0, "James Harden"], - [0, "Paul George"], - [0, "Dejounte Murray"], - [0, "Russell Westbrook"], - [0, "Luka Doncic"], - [0, "Russell Westbrook"] + [T_NULL, "Dejounte Murray"], + [T_NULL, "James Harden"], + [T_NULL, "Paul George"], + [T_NULL, "Dejounte Murray"], + [T_NULL, "Russell Westbrook"], + [T_NULL, "Luka Doncic"], + [T_NULL, "Russell Westbrook"] ] } self.check_column_names(resp, expected_data["column_names"]) diff --git a/tests/query/stateless/test_new_groupby.py b/tests/query/v1/test_new_groupby.py similarity index 100% rename from tests/query/stateless/test_new_groupby.py rename to tests/query/v1/test_new_groupby.py