diff --git a/src/graph/context/ast/QueryAstContext.h b/src/graph/context/ast/QueryAstContext.h index 2146f54972c..74fd7bdbd7d 100644 --- a/src/graph/context/ast/QueryAstContext.h +++ b/src/graph/context/ast/QueryAstContext.h @@ -95,12 +95,10 @@ struct GoContext final : AstContext { bool joinInput{false}; // true when $$.tag.prop exist bool joinDst{false}; - // true when yield clause only yield distinct dst id - bool onlyYieldDistinctDstId{false}; - // true when edge props only use dst id - bool edgePropsOnlyUseDstId{false}; // Optimize for some simple go sentence which only need dst id. bool isSimple{false}; + // The column name used by plan node`GetDstBySrc` + std::string dstIdColName{kDst}; ExpressionProps exprProps; diff --git a/src/graph/planner/ngql/GoPlanner.cpp b/src/graph/planner/ngql/GoPlanner.cpp index 4c312f6b2fa..be66d973a15 100644 --- a/src/graph/planner/ngql/GoPlanner.cpp +++ b/src/graph/planner/ngql/GoPlanner.cpp @@ -310,39 +310,40 @@ PlanNode* GoPlanner::buildLastStepJoinPlan(PlanNode* gn, PlanNode* join) { PlanNode* GoPlanner::lastStep(PlanNode* dep, PlanNode* join) { auto qctx = goCtx_->qctx; - PlanNode* scan = nullptr; + PlanNode* cur = nullptr; - if (!goCtx_->joinInput && goCtx_->limits.empty() && goCtx_->onlyYieldDistinctDstId && - !goCtx_->exprProps.hasSrcTagProperty() && goCtx_->edgePropsOnlyUseDstId) { + if (goCtx_->isSimple) { auto* gd = GetDstBySrc::make(qctx, dep, goCtx_->space.id); gd->setSrc(goCtx_->from.src); gd->setEdgeTypes(buildEdgeTypes()); gd->setInputVar(goCtx_->vidsVar); - gd->setColNames(goCtx_->colNames); + gd->setColNames({goCtx_->dstIdColName}); auto* dedup = Dedup::make(qctx, gd); - dedup->setColNames(goCtx_->colNames); - scan = dedup; + cur = dedup; - auto* root = goCtx_->joinDst ? buildJoinDstPlan(scan) : scan; - if (goCtx_->filter != nullptr) { - root = Filter::make(qctx, root, goCtx_->filter); - } if (goCtx_->joinDst) { - goCtx_->yieldExpr->columns()[0]->setExpr(ColumnExpression::make(qctx->objPool(), 0)); - root = Project::make(qctx, root, goCtx_->yieldExpr); + cur = buildJoinDstPlan(cur); } - root->setColNames(std::move(goCtx_->colNames)); - return root; + if (goCtx_->filter) { + cur = Filter::make(qctx, cur, goCtx_->filter); + } + if (goCtx_->joinDst || goCtx_->yieldExpr->columns().size() != 1) { + cur = Project::make(qctx, cur, goCtx_->yieldExpr); + } else { + gd->setColNames(goCtx_->colNames); + dedup->setColNames(goCtx_->colNames); + } + cur->setColNames(goCtx_->colNames); + return cur; } else { auto* gn = GetNeighbors::make(qctx, dep, goCtx_->space.id); gn->setSrc(goCtx_->from.src); gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); gn->setEdgeProps(buildEdgeProps(false)); gn->setInputVar(goCtx_->vidsVar); - scan = gn; const auto& steps = goCtx_->steps; - auto* sampleLimit = buildSampleLimit(scan, steps.isMToN() ? steps.nSteps() : steps.steps()); + auto* sampleLimit = buildSampleLimit(gn, steps.isMToN() ? steps.nSteps() : steps.steps()); auto* root = buildLastStepJoinPlan(sampleLimit, join); @@ -413,13 +414,33 @@ SubPlan GoPlanner::oneStepPlan(SubPlan& startVidPlan) { auto isSimple = goCtx_->isSimple; PlanNode* scan = nullptr; + PlanNode* cur = nullptr; if (isSimple) { auto* gd = GetDstBySrc::make(qctx, startVidPlan.root, goCtx_->space.id); gd->setSrc(goCtx_->from.src); gd->setEdgeTypes(buildEdgeTypes()); gd->setInputVar(goCtx_->vidsVar); - gd->setColNames({kDst}); + gd->setColNames({goCtx_->dstIdColName}); scan = gd; + + auto* dedup = Dedup::make(qctx, gd); + dedup->setColNames(gd->colNames()); + cur = dedup; + if (goCtx_->joinDst) { + cur = buildJoinDstPlan(cur); + } + + if (goCtx_->filter != nullptr) { + cur = Filter::make(qctx, cur, goCtx_->filter); + } + + if (goCtx_->joinDst || goCtx_->yieldExpr->columns().size() != 1) { + cur = Project::make(qctx, cur, goCtx_->yieldExpr); + } else { + gd->setColNames(goCtx_->colNames); + dedup->setColNames(goCtx_->colNames); + } + cur->setColNames(std::move(goCtx_->colNames)); } else { auto* gn = GetNeighbors::make(qctx, startVidPlan.root, goCtx_->space.id); gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); @@ -427,26 +448,25 @@ SubPlan GoPlanner::oneStepPlan(SubPlan& startVidPlan) { gn->setSrc(goCtx_->from.src); gn->setInputVar(goCtx_->vidsVar); scan = gn; - } - - auto* sampleLimit = buildSampleLimit(scan, 1 /* one step */); - SubPlan subPlan; - subPlan.tail = startVidPlan.tail != nullptr ? startVidPlan.tail : scan; - subPlan.root = buildOneStepJoinPlan(sampleLimit); + auto* sampleLimit = buildSampleLimit(gn, 1 /* one step */); + cur = sampleLimit; + cur = buildOneStepJoinPlan(sampleLimit); - if (goCtx_->filter != nullptr) { - subPlan.root = Filter::make(qctx, subPlan.root, goCtx_->filter); - } + if (goCtx_->filter != nullptr) { + cur = Filter::make(qctx, cur, goCtx_->filter); + } - if (!isSimple) { - subPlan.root = Project::make(qctx, subPlan.root, goCtx_->yieldExpr); - } - subPlan.root->setColNames(std::move(goCtx_->colNames)); - if (goCtx_->distinct) { - subPlan.root = Dedup::make(qctx, subPlan.root); + cur = Project::make(qctx, cur, goCtx_->yieldExpr); + cur->setColNames(std::move(goCtx_->colNames)); + if (goCtx_->distinct) { + cur = Dedup::make(qctx, cur); + } } + SubPlan subPlan; + subPlan.root = cur; + subPlan.tail = startVidPlan.tail != nullptr ? startVidPlan.tail : scan; return subPlan; } @@ -455,43 +475,40 @@ SubPlan GoPlanner::nStepsPlan(SubPlan& startVidPlan) { loopStepVar_ = qctx->vctx()->anonVarGen()->getVar(); auto* start = StartNode::make(qctx); - PlanNode* scan = nullptr; + PlanNode* getDst = nullptr; + + PlanNode* loopBody = nullptr; + PlanNode* loopDep = startVidPlan.root; if (!goCtx_->joinInput && goCtx_->limits.empty()) { auto* gd = GetDstBySrc::make(qctx, start, goCtx_->space.id); gd->setSrc(goCtx_->from.src); gd->setEdgeTypes(buildEdgeTypes()); gd->setInputVar(goCtx_->vidsVar); - gd->setColNames({kDst}); - scan = gd; + gd->setColNames({goCtx_->dstIdColName}); + auto* dedup = Dedup::make(qctx, gd); + dedup->setOutputVar(goCtx_->vidsVar); + getDst = dedup; + + loopBody = getDst; } else { auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); gn->setSrc(goCtx_->from.src); gn->setEdgeProps(buildEdgeProps(true)); gn->setInputVar(goCtx_->vidsVar); - scan = gn; - } - auto* sampleLimit = buildSampleLimit(scan); + auto* sampleLimit = buildSampleLimit(gn); - PlanNode* getDst = nullptr; - if (!goCtx_->joinInput && goCtx_->limits.empty()) { - auto* dedup = Dedup::make(qctx, sampleLimit); - dedup->setOutputVar(goCtx_->vidsVar); - dedup->setColNames(goCtx_->colNames); - getDst = dedup; - } else { getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); - } - PlanNode* loopBody = getDst; - PlanNode* loopDep = startVidPlan.root; - if (goCtx_->joinInput) { - auto* joinLeft = extractVidFromRuntimeInput(startVidPlan.root); - auto* joinRight = extractSrcDstFromGN(getDst, sampleLimit->outputVar()); - loopBody = trackStartVid(joinLeft, joinRight); - loopDep = joinLeft; + loopBody = getDst; + if (goCtx_->joinInput) { + auto* joinLeft = extractVidFromRuntimeInput(startVidPlan.root); + auto* joinRight = extractSrcDstFromGN(getDst, sampleLimit->outputVar()); + loopBody = trackStartVid(joinLeft, joinRight); + loopDep = joinLeft; + } } - auto* condition = loopCondition(goCtx_->steps.steps() - 1, sampleLimit->outputVar()); + auto* condition = loopCondition(goCtx_->steps.steps() - 1, getDst->outputVar()); auto* loop = Loop::make(qctx, loopDep, loopBody, condition); auto* root = lastStep(loop, loopBody == getDst ? nullptr : loopBody); @@ -512,62 +529,74 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { auto* start = StartNode::make(qctx); - PlanNode* scan = nullptr; + PlanNode* getDst = nullptr; + + PlanNode* loopBody = nullptr; + PlanNode* loopDep = startVidPlan.root; if (isSimple) { auto* gd = GetDstBySrc::make(qctx, start, goCtx_->space.id); gd->setSrc(goCtx_->from.src); gd->setEdgeTypes(buildEdgeTypes()); gd->setInputVar(goCtx_->vidsVar); - gd->setColNames({kDst}); - scan = gd; + gd->setColNames({goCtx_->dstIdColName}); + auto* dedup = Dedup::make(qctx, gd); + // The outputVar of `Dedup` is the same as the inputVar of `GetDstBySrc`. + // So the output of `Dedup` of current iteration feeds into the input of `GetDstBySrc` of next + // iteration. + dedup->setOutputVar(goCtx_->vidsVar); + dedup->setColNames(gd->colNames()); + getDst = dedup; + loopBody = getDst; + + if (joinDst) { + loopBody = extractDstId(loopBody); + // Left join, join the dst id with `GetVertices` + loopBody = buildJoinDstPlan(loopBody); + } + if (goCtx_->filter) { + loopBody = Filter::make(qctx, loopBody, goCtx_->filter); + } + if (joinDst || goCtx_->yieldExpr->columns().size() != 1) { + loopBody = Project::make(qctx, loopBody, goCtx_->yieldExpr); + } else { + gd->setColNames(goCtx_->colNames); + dedup->setColNames(goCtx_->colNames); + } + loopBody->setColNames(goCtx_->colNames); } else { auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); gn->setSrc(goCtx_->from.src); gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); gn->setEdgeProps(buildEdgeProps(false)); gn->setInputVar(goCtx_->vidsVar); - scan = gn; - } - - auto* sampleLimit = buildSampleLimit(scan); - - PlanNode* getDst = nullptr; + auto* sampleLimit = buildSampleLimit(gn); - if (isSimple) { - auto* dedup = Dedup::make(qctx, sampleLimit); - dedup->setOutputVar(goCtx_->vidsVar); - dedup->setColNames(sampleLimit->colNames()); - getDst = dedup; - } else { getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); - } - auto* loopBody = getDst; - auto* loopDep = startVidPlan.root; - PlanNode* trackVid = nullptr; - if (joinInput) { - auto* joinLeft = extractVidFromRuntimeInput(startVidPlan.root); - auto* joinRight = extractSrcDstFromGN(getDst, sampleLimit->outputVar()); - trackVid = trackStartVid(joinLeft, joinRight); - loopBody = trackVid; - loopDep = joinLeft; - } + loopBody = getDst; + PlanNode* trackVid = nullptr; + if (joinInput) { + auto* joinLeft = extractVidFromRuntimeInput(startVidPlan.root); + auto* joinRight = extractSrcDstFromGN(getDst, sampleLimit->outputVar()); + trackVid = trackStartVid(joinLeft, joinRight); + loopBody = trackVid; + loopDep = joinLeft; + } - if (joinInput || joinDst) { - loopBody = extractSrcEdgePropsFromGN(loopBody, sampleLimit->outputVar()); - loopBody = joinDst ? buildJoinDstPlan(loopBody) : loopBody; - loopBody = joinInput ? lastStepJoinInput(trackVid, loopBody) : loopBody; - loopBody = joinInput ? buildJoinInputPlan(loopBody) : loopBody; - } + if (joinInput || joinDst) { + loopBody = extractSrcEdgePropsFromGN(loopBody, sampleLimit->outputVar()); + loopBody = joinDst ? buildJoinDstPlan(loopBody) : loopBody; + loopBody = joinInput ? lastStepJoinInput(trackVid, loopBody) : loopBody; + loopBody = joinInput ? buildJoinInputPlan(loopBody) : loopBody; + } - if (goCtx_->filter) { - const auto& filterInput = - (joinInput || joinDst) ? loopBody->outputVar() : sampleLimit->outputVar(); - loopBody = Filter::make(qctx, loopBody, goCtx_->filter); - loopBody->setInputVar(filterInput); - } + if (goCtx_->filter) { + const auto& filterInput = + (joinInput || joinDst) ? loopBody->outputVar() : sampleLimit->outputVar(); + loopBody = Filter::make(qctx, loopBody, goCtx_->filter); + loopBody->setInputVar(filterInput); + } - if (!isSimple) { const auto& projectInput = (loopBody != getDst) ? loopBody->outputVar() : sampleLimit->outputVar(); loopBody = Project::make(qctx, loopBody, goCtx_->yieldExpr); @@ -576,25 +605,24 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { if (goCtx_->distinct) { loopBody = Dedup::make(qctx, loopBody); } + loopBody->setColNames(goCtx_->colNames); } - loopBody->setColNames(std::move(goCtx_->colNames)); - auto* condition = loopCondition(goCtx_->steps.nSteps(), sampleLimit->outputVar()); + auto* condition = loopCondition(goCtx_->steps.nSteps(), getDst->outputVar()); auto* loop = Loop::make(qctx, loopDep, loopBody, condition); auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kMToN); dc->addDep(loop); - if (isSimple) { + if (loopBody == getDst) { StepClause newStep(goCtx_->steps.mSteps() + 1, goCtx_->steps.nSteps() + 1); dc->setMToN(newStep); - dc->setInputVars({loopBody->outputVar()}); } else { dc->setMToN(goCtx_->steps); - dc->setInputVars({loopBody->outputVar()}); } + dc->setInputVars({loopBody->outputVar()}); dc->setDistinct(goCtx_->distinct); - dc->setColNames(loopBody->colNames()); + dc->setColNames(goCtx_->colNames); SubPlan subPlan; subPlan.root = dc; @@ -606,18 +634,8 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { StatusOr GoPlanner::transform(AstContext* astCtx) { goCtx_ = static_cast(astCtx); auto qctx = goCtx_->qctx; - goCtx_->joinInput = goCtx_->from.fromType != FromType::kInstantExpr; + goCtx_->joinInput = goCtx_->from.fromType != FromType::kInstantExpr && !goCtx_->isSimple; goCtx_->joinDst = !goCtx_->exprProps.dstTagProps().empty(); - goCtx_->onlyYieldDistinctDstId = onlyYieldDistinctDstId(); - goCtx_->edgePropsOnlyUseDstId = edgePropsOnlyUseDstId(); - goCtx_->isSimple = isSimpleCase(); - if (goCtx_->isSimple) { - // We don't need to do a inner join in such case. - goCtx_->joinInput = false; - } - if (goCtx_->isSimple) { - goCtx_->yieldExpr->columns()[0]->setExpr(InputPropertyExpression::make(qctx->objPool(), kDst)); - } SubPlan startPlan = PlannerUtil::buildStart(qctx, goCtx_->from, goCtx_->vidsVar); @@ -640,53 +658,11 @@ StatusOr GoPlanner::transform(AstContext* astCtx) { return nStepsPlan(startPlan); } -bool GoPlanner::isSimpleCase() { - if (!goCtx_->onlyYieldDistinctDstId) { - return false; - } - if (goCtx_->joinDst || goCtx_->filter || !goCtx_->limits.empty()) { - return false; - } - auto& exprProps = goCtx_->exprProps; - if (!exprProps.srcTagProps().empty()) return false; - if (!exprProps.dstTagProps().empty()) return false; - - return goCtx_->edgePropsOnlyUseDstId; -} - -bool GoPlanner::edgePropsOnlyUseDstId() { - for (auto& edgeProp : goCtx_->exprProps.edgeProps()) { - auto props = edgeProp.second; - if (props.size() != 1) return false; - if (props.find(kDst) == props.end()) return false; - } - - return true; -} - -bool GoPlanner::onlyYieldDistinctDstId() { - if (!goCtx_->distinct) return false; - if (goCtx_->yieldExpr->columns().size() != 1) { - return false; - } - if (goCtx_->yieldExpr->columns()[0]->expr()->kind() != Expression::Kind::kEdgeDst && - goCtx_->yieldExpr->columns()[0]->expr()->kind() != Expression::Kind::kVarProperty) { - return false; - } - auto* expr = goCtx_->yieldExpr->columns()[0]->expr(); - if (expr->kind() == Expression::Kind::kEdgeDst) { - auto dstExpr = static_cast(expr); - if (dstExpr->sym() != "*" && goCtx_->over.edgeTypes.size() != 1) { - return false; - } - } - return true; -} - std::vector GoPlanner::buildEdgeTypes() { switch (goCtx_->over.direction) { case storage::cpp2::EdgeDirection::IN_EDGE: { std::vector edgeTypes; + edgeTypes.reserve(goCtx_->over.edgeTypes.size()); for (auto edgeType : goCtx_->over.edgeTypes) { edgeTypes.emplace_back(-edgeType); } @@ -707,5 +683,15 @@ std::vector GoPlanner::buildEdgeTypes() { return {}; } +PlanNode* GoPlanner::extractDstId(PlanNode* node) { + auto pool = goCtx_->qctx->objPool(); + auto* columns = pool->makeAndAdd(); + auto* column = new YieldColumn(ColumnExpression::make(pool, 0)); + columns->addColumn(column); + auto* project = Project::make(goCtx_->qctx, node, columns); + project->setColNames({goCtx_->dstIdColName}); + return project; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/planner/ngql/GoPlanner.h b/src/graph/planner/ngql/GoPlanner.h index cd196cc0422..563ef542514 100644 --- a/src/graph/planner/ngql/GoPlanner.h +++ b/src/graph/planner/ngql/GoPlanner.h @@ -88,11 +88,8 @@ class GoPlanner final : public Planner { // Get step sample/limit number Expression* stepSampleLimit(); - - bool isSimpleCase(); - bool edgePropsOnlyUseDstId(); - bool onlyYieldDistinctDstId(); std::vector buildEdgeTypes(); + PlanNode* extractDstId(PlanNode* node); private: GoPlanner() = default; diff --git a/src/graph/validator/GoValidator.cpp b/src/graph/validator/GoValidator.cpp index 8f070ffb742..b7884da2d50 100644 --- a/src/graph/validator/GoValidator.cpp +++ b/src/graph/validator/GoValidator.cpp @@ -58,7 +58,21 @@ Status GoValidator::validateImpl() { return Status::SemanticError("Only support single input in a go sentence."); } + goCtx_->isSimple = isSimpleCase(); + if (goCtx_->isSimple) { + // Need to unify all EdgeDstIdExpr to *._dst. + // eg. serve._dst will be unified to *._dst + rewrite2EdgeDst(); + } NG_RETURN_IF_ERROR(buildColumns()); + if (goCtx_->isSimple) { + auto iter = propExprColMap_.find("*._dst"); + if (iter != propExprColMap_.end()) { + goCtx_->dstIdColName = iter->second->alias(); + } + // Rewrite *._dst/serve._dst to $dstIdColName + rewriteEdgeDst2VarProp(); + } return Status::OK(); } @@ -98,8 +112,8 @@ Status GoValidator::validateWhere(WhereClause* where) { return Status::SemanticError(ss.str()); } + goCtx_->filter = rewriteVertexEdge2EdgeProp(filter); NG_RETURN_IF_ERROR(deduceProps(filter, goCtx_->exprProps, &tagIds_, &goCtx_->over.edgeTypes)); - goCtx_->filter = filter; return Status::OK(); } @@ -290,13 +304,13 @@ Status GoValidator::buildColumns() { const auto& inputProps = exprProps.inputProps(); const auto& varProps = exprProps.varProps(); const auto& from = goCtx_->from; + auto pool = qctx_->objPool(); if (dstTagProps.empty() && inputProps.empty() && varProps.empty() && from.fromType == FromType::kInstantExpr) { return Status::OK(); } - auto pool = qctx_->objPool(); if (!exprProps.isAllPropsEmpty() || from.fromType != FromType::kInstantExpr) { goCtx_->srcEdgePropsExpr = pool->makeAndAdd(); } @@ -321,9 +335,84 @@ Status GoValidator::buildColumns() { extractPropExprs(col->expr(), uniqueEdgeVertexExpr); newYieldExpr->addColumn(new YieldColumn(rewrite2VarProp(col->expr()), col->alias())); } + goCtx_->yieldExpr = newYieldExpr; return Status::OK(); } +bool GoValidator::isSimpleCase() { + // Check limit clause + if (!goCtx_->limits.empty()) { + return false; + } + // Check if the filter or yield cluase uses: + // 1. src tag props, + // 2. or edge props, except the dst id of edge. + // 3. input or var props. + auto& exprProps = goCtx_->exprProps; + if (!exprProps.srcTagProps().empty()) return false; + if (!exprProps.edgeProps().empty()) { + for (auto& edgeProp : exprProps.edgeProps()) { + auto props = edgeProp.second; + if (props.size() != 1) return false; + if (props.find(kDst) == props.end()) return false; + } + } + if (exprProps.hasInputVarProperty()) return false; + + // Check yield clause + if (!goCtx_->distinct) return false; + bool atLeastOneDstId = false; + for (auto& col : goCtx_->yieldExpr->columns()) { + auto expr = col->expr(); + if (expr->kind() != Expression::Kind::kEdgeDst) continue; + atLeastOneDstId = true; + auto dstIdExpr = static_cast(expr); + if (dstIdExpr->sym() != "*" && goCtx_->over.edgeTypes.size() != 1) { + return false; + } + } + return atLeastOneDstId; +} + +void GoValidator::rewrite2EdgeDst() { + auto matcher = [](const Expression* e) -> bool { + if (e->kind() != Expression::Kind::kEdgeDst) { + return false; + } + auto* edgeDstExpr = static_cast(e); + return edgeDstExpr->sym() != "*"; + }; + auto rewriter = [this](const Expression*) -> Expression* { + return EdgeDstIdExpression::make(qctx_->objPool(), "*"); + }; + + if (goCtx_->filter != nullptr) { + goCtx_->filter = RewriteVisitor::transform(goCtx_->filter, matcher, rewriter); + } + auto* newYieldExpr = qctx_->objPool()->makeAndAdd(); + for (auto* col : goCtx_->yieldExpr->columns()) { + newYieldExpr->addColumn( + new YieldColumn(RewriteVisitor::transform(col->expr(), matcher, rewriter), col->alias())); + } + goCtx_->yieldExpr = newYieldExpr; +} + +void GoValidator::rewriteEdgeDst2VarProp() { + auto matcher = [](const Expression* expr) { return expr->kind() == Expression::Kind::kEdgeDst; }; + auto rewriter = [this](const Expression*) { + return VariablePropertyExpression::make(qctx_->objPool(), "", goCtx_->dstIdColName); + }; + if (goCtx_->filter != nullptr) { + goCtx_->filter = RewriteVisitor::transform(goCtx_->filter, matcher, rewriter); + } + auto* newYieldExpr = qctx_->objPool()->makeAndAdd(); + for (auto* col : goCtx_->yieldExpr->columns()) { + newYieldExpr->addColumn( + new YieldColumn(RewriteVisitor::transform(col->expr(), matcher, rewriter), col->alias())); + } + goCtx_->yieldExpr = newYieldExpr; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/validator/GoValidator.h b/src/graph/validator/GoValidator.h index b3fc435f140..8299c74b569 100644 --- a/src/graph/validator/GoValidator.h +++ b/src/graph/validator/GoValidator.h @@ -42,6 +42,10 @@ class GoValidator final : public Validator { Expression* rewriteVertexEdge2EdgeProp(const Expression* expr); + bool isSimpleCase(); + void rewrite2EdgeDst(); + void rewriteEdgeDst2VarProp(); + private: std::unique_ptr goCtx_; diff --git a/tests/tck/conftest.py b/tests/tck/conftest.py index be47af9b5ad..6d5dc531540 100644 --- a/tests/tck/conftest.py +++ b/tests/tck/conftest.py @@ -811,7 +811,8 @@ def drop_used_space(exec_ctx): @then(parse("the execution plan should be:\n{plan}")) -def check_plan(plan, exec_ctx): +def check_plan(request, plan, exec_ctx): + ngql = exec_ctx["ngql"] resp = exec_ctx["result_set"] expect = table(plan) column_names = expect.get('column_names', []) @@ -821,7 +822,19 @@ def check_plan(plan, exec_ctx): row[idx] = [int(cell.strip()) for cell in row[idx].split(",") if len(cell) > 0] rows[i] = row differ = PlanDiffer(resp.plan_desc(), expect) - assert differ.diff(), differ.err_msg() + + res = differ.diff() + if not res: + scen = request.function.__scenario__ + feature = scen.feature.rel_filename + location = f"{feature}:{line_number(scen._steps, plan)}" + msg = [ + f"Fail to exec: {ngql}", + f"Location: {location}", + differ.err_msg(), + ] + + assert res, "\n".join(msg) @when(parse("executing query via graph {index:d}:\n{query}")) diff --git a/tests/tck/features/go/SimpleCase.feature b/tests/tck/features/go/SimpleCase.feature index c8dd2e7020b..ef7ba86f5b9 100644 --- a/tests/tck/features/go/SimpleCase.feature +++ b/tests/tck/features/go/SimpleCase.feature @@ -6,7 +6,7 @@ Feature: Simple case Background: Given a graph with space named "nba" - Scenario: go 1 steps yield distinct dst id + Scenario: go 1 step When profiling query: """ GO FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) as dst | YIELD count(*) @@ -20,6 +20,126 @@ Feature: Simple case | 2 | Dedup | 1 | | | 1 | GetDstBySrc | 0 | | | 0 | Start | | | + When profiling query: + """ + GO FROM "Yao Ming" OVER like YIELD DISTINCT id($$) AS dst, $$.player.age AS age | ORDER BY $-.dst + """ + Then the result should be, in any order, with relax comparison: + | dst | age | + | "Shaquille O'Neal" | 47 | + | "Tracy McGrady" | 39 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 7 | Sort | 6 | | + | 6 | Project | 5 | | + | 5 | LeftJoin | 4 | | + | 4 | Project | 3 | | + | 3 | GetVertices | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Yao Ming" OVER like WHERE $$.player.age > 40 YIELD DISTINCT id($$) AS dst, $$.player.age AS age | ORDER BY $-.dst + """ + Then the result should be, in any order, with relax comparison: + | dst | age | + | "Shaquille O'Neal" | 47 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 8 | Sort | 7 | | + | 7 | Project | 6 | | + | 6 | Filter | 5 | | + | 5 | LeftJoin | 4 | | + | 4 | Project | 3 | | + | 3 | GetVertices | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Yao Ming" OVER like WHERE $$.player.age > 40 AND id($$) != "Tony Parker" YIELD DISTINCT id($$) AS dst, id($$) AS dst2, $$.player.age + 100 AS age | ORDER BY $-.dst + """ + Then the result should be, in any order, with relax comparison: + | dst | dst2 | age | + | "Shaquille O'Neal" | "Shaquille O'Neal" | 147 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 8 | Sort | 7 | | + | 7 | Project | 11 | | + | 11 | Filter | 10 | | + | 10 | LeftJoin | 9 | | + | 9 | Filter | 4 | | + | 4 | Project | 3 | | + | 3 | GetVertices | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Tony Parker" OVER serve, like WHERE serve._dst !="abc" YIELD DISTINCT id($$) AS a | ORDER BY $-.a + """ + Then the result should be, in any order, with relax comparison: + | a | + | "Hornets" | + | "LaMarcus Aldridge" | + | "Manu Ginobili" | + | "Spurs" | + | "Tim Duncan" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 4 | Sort | 3 | | + | 3 | Filter | 3 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Tony Parker" OVER like YIELD DISTINCT id($$) AS a | ORDER BY $-.a + """ + Then the result should be, in any order, with relax comparison: + | a | + | "LaMarcus Aldridge" | + | "Manu Ginobili" | + | "Tim Duncan" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 3 | Sort | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Tony Parker" OVER like YIELD DISTINCT 2, id($$) AS a | ORDER BY $-.a + """ + Then the result should be, in any order, with relax comparison: + | 2 | a | + | 2 | "LaMarcus Aldridge" | + | 2 | "Manu Ginobili" | + | 2 | "Tim Duncan" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 4 | Sort | 3 | | + | 3 | Project | 3 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + When profiling query: + """ + GO FROM "Tony Parker" OVER like WHERE like._dst != "Tim Duncan" YIELD DISTINCT id($$), 2, like._dst AS a | ORDER BY $-.a + """ + Then the result should be, in any order, with relax comparison: + | id($$) | 2 | a | + | "LaMarcus Aldridge" | 2 | "LaMarcus Aldridge" | + | "Manu Ginobili" | 2 | "Manu Ginobili" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 5 | Sort | 4 | | + | 4 | Project | 3 | | + | 3 | Filter | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | Scenario: go m steps When profiling query: @@ -61,6 +181,28 @@ Feature: Simple case | 2 | GetDstBySrc | 1 | | | 1 | Start | | | | 0 | Start | | | + When profiling query: + """ + GO 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT WHERE id($$) != "Not exists" YIELD DISTINCT id($$), $$.player.age | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 22 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 12 | Aggregate | 11 | | + | 11 | Project | 14 | | + | 14 | LeftJoin | 13 | | + | 13 | Filter | 8 | | + | 8 | Project | 7 | | + | 7 | GetVertices | 6 | | + | 6 | Dedup | 5 | | + | 5 | GetDstBySrc | 4 | | + | 4 | Loop | 0 | {"loopBody": "3"} | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | # The last step degenerates to `GetNeighbors` when the yield clause is not `YIELD DISTINCT id($$)` When profiling query: """ @@ -96,6 +238,27 @@ Feature: Simple case | 2 | GetDstBySrc | 1 | | | 1 | Start | | | | 0 | Start | | | + When profiling query: + """ + GO 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT $$.team.name, id($$) AS dst | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 22 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 11 | Aggregate | 10 | | + | 10 | Project | 9 | | + | 9 | LeftJoin | 8 | | + | 8 | Project | 7 | | + | 7 | GetVertices | 6 | | + | 6 | Dedup | 5 | | + | 5 | GetDstBySrc | 4 | | + | 4 | Loop | 0 | {"loopBody": "3"} | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | When profiling query: """ GO 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT WHERE $^.player.age > 30 YIELD DISTINCT id($$) AS dst | YIELD count(*) @@ -157,6 +320,29 @@ Feature: Simple case | 2 | GetDstBySrc | 1 | | | 1 | Start | | | | 0 | Start | | | + When profiling query: + """ + YIELD "Tony Parker" as a | GO 3 STEPS FROM $-.a OVER serve BIDIRECT YIELD DISTINCT $$.team.name, id($$) AS dst | YIELD COUNT(*) + """ + Then the result should be, in any order, with relax comparison: + | COUNT(*) | + | 22 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 14 | Aggregate | 13 | | + | 13 | Project | 12 | | + | 12 | LeftJoin | 11 | | + | 11 | Project | 10 | | + | 10 | GetVertices | 9 | | + | 9 | Dedup | 8 | | + | 8 | GetDstBySrc | 7 | | + | 7 | Loop | 3 | {"loopBody": "6"} | + | 6 | Dedup | 5 | | + | 5 | GetDstBySrc | 4 | | + | 4 | Start | | | + | 3 | Dedup | 15 | | + | 15 | Project | 0 | | + | 0 | Start | | | # Because GetDstBySrc doesn't support limit push down, so degenrate to GetNeighbors when the query contains limit/simple clause When profiling query: """ @@ -180,7 +366,7 @@ Feature: Simple case | 1 | Start | | | | 0 | Start | | | - Scenario: go m to n steps yield distinct dst id + Scenario: go m to n steps When profiling query: """ GO 1 to 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) AS dst | YIELD count(*) @@ -197,6 +383,149 @@ Feature: Simple case | 2 | GetDstBySrc | 1 | | | 1 | Start | | | | 0 | Start | | | + When profiling query: + """ + GO 1 to 3 STEPS FROM "Tony Parker" OVER like WHERE like._dst != "Yao Ming" YIELD DISTINCT id($$) AS a | ORDER BY $-.a + """ + Then the result should be, in any order, with relax comparison: + | a | + | "LaMarcus Aldridge" | + | "Manu Ginobili" | + | "Tim Duncan" | + | "Tony Parker" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 7 | Sort | 6 | | + | 6 | DataCollect | 5 | | + | 5 | Loop | 0 | {"loopBody": "4"} | + | 4 | Filter | 3 | | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 3 STEP FROM "Tony Parker" OVER like WHERE $$.player.age > 40 YIELD DISTINCT id($$), $$.player.age as age, $$.player.name | ORDER BY $-.age + """ + Then the result should be, in any order, with relax comparison: + | id($$) | age | $$.player.name | + | "Manu Ginobili" | 41 | "Manu Ginobili" | + | "Tim Duncan" | 42 | "Tim Duncan" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 12 | Sort | 11 | | + | 11 | DataCollect | 10 | | + | 10 | Loop | 0 | {"loopBody": "9"} | + | 9 | Project | 8 | | + | 8 | Filter | 7 | | + | 7 | LeftJoin | 6 | | + | 6 | Project | 5 | | + | 5 | GetVertices | 4 | | + | 4 | Project | 3 | | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 3 STEP FROM "Tony Parker" OVER like WHERE id($$) != "Tim Duncan" YIELD DISTINCT id($$), $$.player.age as age, $$.player.name | ORDER BY $-.age + """ + Then the result should be, in any order, with relax comparison: + | id($$) | age | $$.player.name | + | "LaMarcus Aldridge" | 33 | "LaMarcus Aldridge" | + | "Tony Parker" | 36 | "Tony Parker" | + | "Manu Ginobili" | 41 | "Manu Ginobili" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 12 | Sort | 11 | | + | 11 | DataCollect | 10 | | + | 10 | Loop | 0 | {"loopBody": "9"} | + | 9 | Project | 14 | | + | 14 | LeftJoin | 13 | | + | 13 | Filter | 6 | | + | 6 | Project | 5 | | + | 5 | GetVertices | 4 | | + | 4 | Project | 3 | | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT 3, id($$) AS dst | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 41 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 7 | Aggregate | 6 | | + | 6 | DataCollect | 5 | | + | 5 | Loop | 0 | {"loopBody": "4"} | + | 4 | Project | 3 | | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT $$.player.age AS age, id($$) | YIELD COUNT($-.age) + """ + Then the result should be, in any order, with relax comparison: + | COUNT($-.age) | + | 19 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 11 | Aggregate | 10 | | + | 10 | DataCollect | 9 | | + | 9 | Loop | 0 | {"loopBody": "8"} | + | 8 | Project | 7 | | + | 7 | LeftJoin | 6 | | + | 6 | Project | 5 | | + | 5 | GetVertices | 4 | | + | 4 | Project | 3 | | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 8 steps FROM "Tony Parker" OVER serve, like YIELD distinct like._dst AS a | YIELD COUNT($-.a) + """ + Then the result should be, in any order, with relax comparison: + | COUNT($-.a) | + | 4 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 9 | Aggregate | 8 | | + | 8 | DataCollect | 7 | | + | 7 | Loop | 0 | {"loopBody": "6"} | + | 6 | Dedup | 5 | | + | 5 | Project | 4 | | + | 4 | Dedup | 3 | | + | 3 | Project | 2 | | + | 2 | GetNeighbors | 1 | | + | 1 | Start | | | + | 0 | Start | | | + When profiling query: + """ + GO 1 to 8 steps FROM "Tony Parker" OVER serve, like YIELD DISTINCT serve._dst AS a | YIELD COUNT($-.a) + """ + Then the result should be, in any order, with relax comparison: + | COUNT($-.a) | + | 3 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 9 | Aggregate | 8 | | + | 8 | DataCollect | 7 | | + | 7 | Loop | 0 | {"loopBody": "6"} | + | 6 | Dedup | 5 | | + | 5 | Project | 4 | | + | 4 | Dedup | 3 | | + | 3 | Project | 2 | | + | 2 | GetNeighbors | 1 | | + | 1 | Start | | | + | 0 | Start | | | Scenario: k-hop neighbors When profiling query: @@ -261,3 +590,36 @@ Feature: Simple case | 2 | Dedup | 1 | | | 1 | GetDstBySrc | 0 | | | 0 | Start | | | + When profiling query: + """ + GO FROM "Yao Ming" OVER like YIELD DISTINCT id($$) AS aa | GO 1 to 3 STEP FROM $-.aa OVER like WHERE id($$) != "Tim Duncan" YIELD DISTINCT id($$), $$.player.age as age, $$.player.name | ORDER BY $-.age + """ + Then the result should be, in any order, with relax comparison: + | id($$) | age | $$.player.name | + | "JaVale McGee" | 31 | "JaVale McGee" | + | "Rudy Gay" | 32 | "Rudy Gay" | + | "LaMarcus Aldridge" | 33 | "LaMarcus Aldridge" | + | "Tony Parker" | 36 | "Tony Parker" | + | "Tracy McGrady" | 39 | "Tracy McGrady" | + | "Kobe Bryant" | 40 | "Kobe Bryant" | + | "Manu Ginobili" | 41 | "Manu Ginobili" | + | "Grant Hill" | 46 | "Grant Hill" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 16 | Sort | 15 | | + | 15 | DataCollect | 14 | | + | 14 | Loop | 4 | {"loopBody": "13"} | + | 13 | Project | 18 | | + | 18 | LeftJoin | 17 | | + | 17 | Filter | 10 | | + | 10 | Project | 9 | | + | 9 | GetVertices | 8 | | + | 8 | Project | 7 | | + | 7 | Dedup | 6 | | + | 6 | GetDstBySrc | 5 | | + | 5 | Start | | | + | 4 | Dedup | 3 | | + | 3 | Project | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | |