From 8729d7ef76af93fe7317e7fcb2aa30fe05d36add Mon Sep 17 00:00:00 2001 From: xiyang Date: Sat, 25 Mar 2023 01:59:08 -0400 Subject: [PATCH] refactor INL-join planning --- src/include/planner/join_order_enumerator.h | 20 +- .../planner/join_order_enumerator_context.h | 27 ++- .../planner/logical_plan/logical_plan_util.h | 5 - src/planner/join_order_enumerator.cpp | 183 ++++++++++++------ src/planner/join_order_enumerator_context.cpp | 21 +- src/planner/operator/logical_plan_util.cpp | 18 -- src/planner/query_planner.cpp | 9 +- 7 files changed, 182 insertions(+), 101 deletions(-) diff --git a/src/include/planner/join_order_enumerator.h b/src/include/planner/join_order_enumerator.h index 63383a931f4..c28aa18c7e7 100644 --- a/src/include/planner/join_order_enumerator.h +++ b/src/include/planner/join_order_enumerator.h @@ -18,8 +18,6 @@ class JoinOrderEnumeratorContext; * filter push down */ class JoinOrderEnumerator { - friend class ASPOptimizer; - public: JoinOrderEnumerator(const catalog::Catalog& catalog, const storage::NodesStatisticsAndDeletedIDs& nodesStatistics, @@ -32,8 +30,7 @@ class JoinOrderEnumerator { inline void resetState() { context->resetState(); } - std::unique_ptr enterSubquery(LogicalPlan* outerPlan, - binder::expression_vector expressionsToScan, + std::unique_ptr enterSubquery( binder::expression_vector nodeIDsToScanFromInnerAndOuter); void exitSubquery(std::unique_ptr prevContext); @@ -70,13 +67,13 @@ class JoinOrderEnumerator { std::vector> enumerate( QueryGraph* queryGraph, binder::expression_vector& predicates); - void planTableScan(); - + // Level 1 contains base table scans. + void planBaseTableScan(); void planNodeScan(uint32_t nodePos); + void appendScanNodeAndFilter(std::shared_ptr node, LogicalPlan& plan); void planRelScan(uint32_t relPos); - - void planExtendAndFilters(std::shared_ptr rel, common::RelDirection direction, - binder::expression_vector& predicates, LogicalPlan& plan); + void appendExtendAndFilter( + std::shared_ptr rel, common::RelDirection direction, LogicalPlan& plan); void planLevel(uint32_t level); void planLevelExactly(uint32_t level); @@ -89,6 +86,9 @@ class JoinOrderEnumerator { void planInnerJoin(uint32_t leftLevel, uint32_t rightLevel); + bool tryPlanINLJoin(const SubqueryGraph& subgraph, const SubqueryGraph& otherSubgraph, + const std::vector>& joinNodes); + bool canApplyINLJoin(const SubqueryGraph& subgraph, const SubqueryGraph& otherSubgraph, const std::vector>& joinNodes); void planInnerINLJoin(const SubqueryGraph& subgraph, const SubqueryGraph& otherSubgraph, @@ -98,7 +98,7 @@ class JoinOrderEnumerator { // Filter push down for hash join. void planFiltersForHashJoin(binder::expression_vector& predicates, LogicalPlan& plan); - void appendScanNode(std::shared_ptr& node, LogicalPlan& plan); + void appendScanNodeID(std::shared_ptr& node, LogicalPlan& plan); bool needExtendToNewGroup( RelExpression& rel, NodeExpression& boundNode, common::RelDirection direction); diff --git a/src/include/planner/join_order_enumerator_context.h b/src/include/planner/join_order_enumerator_context.h index e0ebe22d0d9..db1d28de24a 100644 --- a/src/include/planner/join_order_enumerator_context.h +++ b/src/include/planner/join_order_enumerator_context.h @@ -13,11 +13,20 @@ class JoinOrderEnumeratorContext { public: JoinOrderEnumeratorContext() : currentLevel{0}, maxLevel{0}, subPlansTable{std::make_unique()}, - queryGraph{nullptr}, outerPlan{nullptr} {} + queryGraph{nullptr} {} - void init(QueryGraph* queryGraph, expression_vector& predicates); + void init(QueryGraph* queryGraph, const expression_vector& predicates); + void initPredicateCollection(const expression_vector& predicates); - inline expression_vector getWhereExpressions() { return whereExpressionsSplitOnAND; } + inline expression_vector getSingleVarPredicates(const Expression& expression) const { + if (!predicateSet->varName2Predicates.contains(expression.getUniqueName())) { + return expression_vector{}; + } + return predicateSet->varName2Predicates.at(expression.getUniqueName()); + } + inline expression_vector getMultiVarPredicates() const { + return predicateSet->multiVarPredicates; + } inline bool containPlans(const SubqueryGraph& subqueryGraph) const { return subPlansTable->containSubgraphPlans(subqueryGraph); @@ -47,7 +56,15 @@ class JoinOrderEnumeratorContext { void resetState(); private: - expression_vector whereExpressionsSplitOnAND; + struct PredicateSet { + // Single-variable predicates + std::unordered_map varName2Predicates; + // Multi-variable predicates + expression_vector multiVarPredicates; + }; + +private: + std::unique_ptr predicateSet; uint32_t currentLevel; uint32_t maxLevel; @@ -55,8 +72,6 @@ class JoinOrderEnumeratorContext { std::unique_ptr subPlansTable; QueryGraph* queryGraph; - LogicalPlan* outerPlan; - expression_vector expressionsToScanFromOuter; expression_vector nodeIDsToScanFromInnerAndOuter; }; diff --git a/src/include/planner/logical_plan/logical_plan_util.h b/src/include/planner/logical_plan/logical_plan_util.h index 86565ef2d54..51aa12531e6 100644 --- a/src/include/planner/logical_plan/logical_plan_util.h +++ b/src/include/planner/logical_plan/logical_plan_util.h @@ -8,16 +8,11 @@ namespace planner { class LogicalPlanUtil { public: - // Return the node whose ID has sequential guarantee on the plan. - static std::shared_ptr getSequentialNode(LogicalPlan& plan); - static inline std::string encodeJoin(LogicalPlan& logicalPlan) { return encodeJoin(logicalPlan.getLastOperator().get()); } private: - static LogicalOperator* getCurrentPipelineSourceOperator(LogicalPlan& plan); - static std::string encodeJoin(LogicalOperator* logicalOperator) { std::string result; encodeJoinRecursive(logicalOperator, result); diff --git a/src/planner/join_order_enumerator.cpp b/src/planner/join_order_enumerator.cpp index 1133b2a4932..e4367e0c4c5 100644 --- a/src/planner/join_order_enumerator.cpp +++ b/src/planner/join_order_enumerator.cpp @@ -73,8 +73,7 @@ std::vector> JoinOrderEnumerator::planCrossProduct( std::vector> JoinOrderEnumerator::enumerate( QueryGraph* queryGraph, expression_vector& predicates) { context->init(queryGraph, predicates); - assert(context->expressionsToScanFromOuter.empty()); - planTableScan(); + planBaseTableScan(); context->currentLevel++; while (context->currentLevel < context->maxLevel) { planLevel(context->currentLevel++); @@ -83,12 +82,9 @@ std::vector> JoinOrderEnumerator::enumerate( } std::unique_ptr JoinOrderEnumerator::enterSubquery( - LogicalPlan* outerPlan, expression_vector expressionsToScan, expression_vector nodeIDsToScanFromInnerAndOuter) { auto prevContext = std::move(context); context = std::make_unique(); - context->outerPlan = outerPlan; - context->expressionsToScanFromOuter = std::move(expressionsToScan); context->nodeIDsToScanFromInnerAndOuter = std::move(nodeIDsToScanFromInnerAndOuter); return prevContext; } @@ -98,7 +94,7 @@ void JoinOrderEnumerator::exitSubquery(std::unique_ptr 1); + assert(level > 2); if (level > MAX_LEVEL_TO_PLAN_EXACTLY) { planLevelApproximately(level); } else { @@ -121,7 +117,7 @@ void JoinOrderEnumerator::planLevelApproximately(uint32_t level) { planInnerJoin(1, level - 1); } -void JoinOrderEnumerator::planTableScan() { +void JoinOrderEnumerator::planBaseTableScan() { auto queryGraph = context->getQueryGraph(); for (auto nodePos = 0u; nodePos < queryGraph->getNumQueryNodes(); ++nodePos) { planNodeScan(nodePos); @@ -136,25 +132,32 @@ void JoinOrderEnumerator::planNodeScan(uint32_t nodePos) { auto newSubgraph = context->getEmptySubqueryGraph(); newSubgraph.addQueryNode(nodePos); auto plan = std::make_unique(); - auto predicates = getNewlyMatchedExpressions( - context->getEmptySubqueryGraph(), newSubgraph, context->getWhereExpressions()); // In un-nested subquery, e.g. MATCH (a) OPTIONAL MATCH (a)-[e1]->(b), the inner query // ("(a)-[e1]->(b)") needs to scan a, which is already scanned in the outer query (a). To avoid // scanning storage twice, we keep track of node table "a" and make sure when planning inner // query, we only scan internal ID of "a". if (!context->nodeToScanFromInnerAndOuter(node.get())) { - appendScanNode(node, *plan); - auto properties = queryPlanner->getPropertiesForNode(*node); - queryPlanner->appendScanNodePropIfNecessary(properties, node, *plan); - queryPlanner->appendFilters(predicates, *plan); + appendScanNodeAndFilter(node, *plan); } else { - appendScanNode(node, *plan); + appendScanNodeID(node, *plan); } context->addPlan(newSubgraph, std::move(plan)); } -static std::pair, std::shared_ptr> -getBoundAndNbrNodes(const RelExpression& rel, RelDirection direction) { +void JoinOrderEnumerator::appendScanNodeAndFilter( + std::shared_ptr node, LogicalPlan& plan) { + appendScanNodeID(node, plan); + auto properties = queryPlanner->getPropertiesForNode(*node); + queryPlanner->appendScanNodePropIfNecessary(properties, node, plan); + auto predicates = context->getSingleVarPredicates(*node); + queryPlanner->appendFilters(predicates, plan); +} + +using bound_nbr_nodes_pair_t = + std::pair, std::shared_ptr>; + +static bound_nbr_nodes_pair_t getBoundAndNbrNodes( + const RelExpression& rel, RelDirection direction) { auto boundNode = direction == FWD ? rel.getSrcNode() : rel.getDstNode(); auto dstNode = direction == FWD ? rel.getDstNode() : rel.getSrcNode(); return make_pair(boundNode, dstNode); @@ -164,22 +167,22 @@ void JoinOrderEnumerator::planRelScan(uint32_t relPos) { auto rel = context->queryGraph->getQueryRel(relPos); auto newSubgraph = context->getEmptySubqueryGraph(); newSubgraph.addQueryRel(relPos); - auto predicates = getNewlyMatchedExpressions( - context->getEmptySubqueryGraph(), newSubgraph, context->getWhereExpressions()); + auto predicates = context->getSingleVarPredicates(*rel); for (auto direction : REL_DIRECTIONS) { auto plan = std::make_unique(); auto [boundNode, _] = getBoundAndNbrNodes(*rel, direction); - appendScanNode(boundNode, *plan); - planExtendAndFilters(rel, direction, predicates, *plan); + appendScanNodeID(boundNode, *plan); + appendExtendAndFilter(rel, direction, *plan); context->addPlan(newSubgraph, std::move(plan)); } } -void JoinOrderEnumerator::planExtendAndFilters(std::shared_ptr rel, - RelDirection direction, expression_vector& predicates, LogicalPlan& plan) { +void JoinOrderEnumerator::appendExtendAndFilter( + std::shared_ptr rel, common::RelDirection direction, LogicalPlan& plan) { auto [boundNode, dstNode] = getBoundAndNbrNodes(*rel, direction); auto properties = queryPlanner->getPropertiesForRel(*rel); appendExtend(boundNode, dstNode, rel, direction, properties, plan); + auto predicates = context->getSingleVarPredicates(*rel); queryPlanner->appendFilters(predicates, plan); } @@ -225,13 +228,54 @@ void JoinOrderEnumerator::planWCOJoin(uint32_t leftLevel, uint32_t rightLevel) { } } +//std::shared_ptr LogicalPlanUtil::getSequentialNode(LogicalPlan& plan) { +// auto pipelineSource = getCurrentPipelineSourceOperator(plan); +// if (pipelineSource->getOperatorType() != LogicalOperatorType::SCAN_NODE) { +// // Pipeline source is not ScanNodeID, meaning at least one sink has happened (e.g. HashJoin) +// // and we loose any sequential guarantees. +// return nullptr; +// } +// return ((LogicalScanNode*)pipelineSource)->getNode(); +//} +// +//LogicalOperator* LogicalPlanUtil::getCurrentPipelineSourceOperator(LogicalPlan& plan) { +// auto op = plan.getLastOperator().get(); +// // Operator with more than one child will be broken into different pipelines. +// while (op->getNumChildren() == 1) { +// op = op->getChild(0).get(); +// } +// assert(op != nullptr); +// return op; +//} + +static LogicalScanNode* getSequentialScanNodeOperator(LogicalOperator* op) { + switch (op->getOperatorType()) { + case LogicalOperatorType::FILTER: + case LogicalOperatorType::SCAN_NODE_PROPERTY: + case LogicalOperatorType::EXTEND: + case LogicalOperatorType::PROJECTION: { // operators we directly search through + return getSequentialScanNodeOperator(op->getChild(0).get()); + } + case LogicalOperatorType::SCAN_NODE: { + return (LogicalScanNode*)op; + } + default: + return nullptr; + } +} + +// Check whether given node ID has sequential guarantee on the plan. +static bool isNodeSequentialOnPlan(LogicalPlan& plan, const NodeExpression& node) { + auto sequentialNode = getSequentialScanNodeOperator(plan.getLastOperator().get())->getNode(); + return sequentialNode != nullptr && sequentialNode->getUniqueName() == node.getUniqueName(); +} + // As a heuristic for wcoj, we always pick rel scan that starts from the bound node. static std::unique_ptr getWCOJBuildPlanForRel( std::vector>& candidatePlans, const NodeExpression& boundNode) { std::unique_ptr result; for (auto& candidatePlan : candidatePlans) { - if (LogicalPlanUtil::getSequentialNode(*candidatePlan)->getUniqueName() == - boundNode.getUniqueName()) { + if (isNodeSequentialOnPlan(*candidatePlan, boundNode)) { assert(result == nullptr); result = candidatePlan->shallowCopy(); } @@ -269,7 +313,7 @@ void JoinOrderEnumerator::planWCOJoin(const SubqueryGraph& subgraph, relPlans.push_back(std::move(relPlan)); } auto predicates = - getNewlyMatchedExpressions(prevSubgraphs, newSubgraph, context->getWhereExpressions()); + getNewlyMatchedExpressions(prevSubgraphs, newSubgraph, context->getMultiVarPredicates()); for (auto& leftPlan : context->getPlans(subgraph)) { auto leftPlanCopy = leftPlan->shallowCopy(); std::vector> rightPlansCopy; @@ -332,39 +376,71 @@ void JoinOrderEnumerator::planInnerJoin(uint32_t leftLevel, uint32_t rightLevel) } } -// Check whether given node ID has sequential guarantee on the plan. -static bool isNodeSequential(LogicalPlan& plan, NodeExpression* node) { - auto sequentialNode = LogicalPlanUtil::getSequentialNode(plan); - return sequentialNode != nullptr && sequentialNode->getUniqueName() == node->getUniqueName(); -} - -// We apply index nested loop join if the following to conditions are satisfied -// - otherSubgraph is an edge; and -// - join node is sequential on at least one plan corresponding to subgraph. (Otherwise INLJ will -// trigger non-sequential read). -bool JoinOrderEnumerator::canApplyINLJoin(const SubqueryGraph& subgraph, +bool JoinOrderEnumerator::tryPlanINLJoin(const SubqueryGraph& subgraph, const SubqueryGraph& otherSubgraph, const std::vector>& joinNodes) { - if (!otherSubgraph.isSingleRel() || joinNodes.size() > 1) { + if (joinNodes.size() > 1) { return false; } - for (auto& plan : context->getPlans(subgraph)) { - if (isNodeSequential(*plan, joinNodes[0].get())) { - return true; + if (!subgraph.isSingleRel() && !otherSubgraph.isSingleRel()) { + return false; + } + if (subgraph.isSingleRel()) { // Always put single rel subgraph to right. + return tryPlanINLJoin(otherSubgraph, subgraph, joinNodes); + } + auto relPos = UINT32_MAX; + for (auto i = 0u; i < context->queryGraph->getNumQueryRels(); ++i) { + if (subgraph.queryRelsSelector[i]) { + relPos = i; } } - return false; -} - -static uint32_t extractJoinRelPos(const SubqueryGraph& subgraph, const QueryGraph& queryGraph) { - for (auto relPos = 0u; relPos < queryGraph.getNumQueryRels(); ++relPos) { - if (subgraph.queryRelsSelector[relPos]) { - return relPos; + assert(relPos != UINT32_MAX); + auto rel = context->queryGraph->getQueryRel(relPos); + auto boundNode = joinNodes[0]; + auto direction = boundNode->getUniqueName() == rel->getSrcNodeName() ? FWD : BWD; + auto newSubgraph = subgraph; + newSubgraph.addQueryRel(relPos); + auto predicates = + getNewlyMatchedExpressions(subgraph, newSubgraph, context->getMultiVarPredicates()); + bool hasAppliedINLJoin = false; + for (auto& prevPlan : context->getPlans(subgraph)) { + if (isNodeSequentialOnPlan(*prevPlan, *boundNode)) { + auto plan = prevPlan->shallowCopy(); + appendExtendAndFilter(rel, direction, *plan); + context->addPlan(newSubgraph, std::move(plan)); + hasAppliedINLJoin = true; } } - throw InternalException("Cannot extract relPos."); + return hasAppliedINLJoin; } +// We apply index nested loop join if the following to conditions are satisfied +// - otherSubgraph is an edge; and +// - join node is sequential on at least one plan corresponding to subgraph. (Otherwise INLJ will +// trigger non-sequential read). +//bool JoinOrderEnumerator::canApplyINLJoin(const SubqueryGraph& subgraph, +// const SubqueryGraph& otherSubgraph, +// const std::vector>& joinNodes) { +// if (!otherSubgraph.isSingleRel() || joinNodes.size() > 1) { +// return false; +// } +// for (auto& plan : context->getPlans(subgraph)) { +// if (isNodeSequential(*plan, joinNodes[0].get())) { +// return true; +// } +// } +// return false; +//} +// +//static uint32_t extractJoinRelPos(const SubqueryGraph& subgraph, const QueryGraph& queryGraph) { +// for (auto relPos = 0u; relPos < queryGraph.getNumQueryRels(); ++relPos) { +// if (subgraph.queryRelsSelector[relPos]) { +// return relPos; +// } +// } +// throw InternalException("Cannot extract relPos."); +//} + void JoinOrderEnumerator::planInnerINLJoin(const SubqueryGraph& subgraph, const SubqueryGraph& otherSubgraph, const std::vector>& joinNodes) { @@ -376,7 +452,7 @@ void JoinOrderEnumerator::planInnerINLJoin(const SubqueryGraph& subgraph, auto newSubgraph = subgraph; newSubgraph.addQueryRel(relPos); auto predicates = - getNewlyMatchedExpressions(subgraph, newSubgraph, context->getWhereExpressions()); + getNewlyMatchedExpressions(subgraph, newSubgraph, context->getMultiVarPredicates()); for (auto& prevPlan : context->getPlans(subgraph)) { if (isNodeSequential(*prevPlan, boundNode)) { auto plan = prevPlan->shallowCopy(); @@ -394,7 +470,7 @@ void JoinOrderEnumerator::planInnerHashJoin(const SubqueryGraph& subgraph, newSubgraph.addSubqueryGraph(otherSubgraph); auto predicates = getNewlyMatchedExpressions(std::vector{subgraph, otherSubgraph}, newSubgraph, - context->getWhereExpressions()); + context->getMultiVarPredicates()); for (auto& leftPlan : context->getPlans(subgraph)) { for (auto& rightPlan : context->getPlans(otherSubgraph)) { auto leftPlanProbeCopy = leftPlan->shallowCopy(); @@ -415,12 +491,11 @@ void JoinOrderEnumerator::planInnerHashJoin(const SubqueryGraph& subgraph, } void JoinOrderEnumerator::planFiltersForHashJoin(expression_vector& predicates, LogicalPlan& plan) { - for (auto& predicate : predicates) { - queryPlanner->appendFilter(predicate, plan); - } + queryPlanner->appendFilters(predicates, plan); } -void JoinOrderEnumerator::appendScanNode(std::shared_ptr& node, LogicalPlan& plan) { +void JoinOrderEnumerator::appendScanNodeID( + std::shared_ptr& node, LogicalPlan& plan) { assert(plan.isEmpty()); auto scan = make_shared(node); scan->computeFactorizedSchema(); diff --git a/src/planner/join_order_enumerator_context.cpp b/src/planner/join_order_enumerator_context.cpp index 01fa2050112..9cb5906d59d 100644 --- a/src/planner/join_order_enumerator_context.cpp +++ b/src/planner/join_order_enumerator_context.cpp @@ -3,8 +3,9 @@ namespace kuzu { namespace planner { -void JoinOrderEnumeratorContext::init(QueryGraph* queryGraph_, expression_vector& predicates) { - whereExpressionsSplitOnAND = predicates; +void JoinOrderEnumeratorContext::init( + QueryGraph* queryGraph_, const expression_vector& predicates) { + initPredicateCollection(predicates); this->queryGraph = queryGraph_; // clear and resize subPlansTable subPlansTable->clear(); @@ -15,6 +16,22 @@ void JoinOrderEnumeratorContext::init(QueryGraph* queryGraph_, expression_vector currentLevel = 1; } +void JoinOrderEnumeratorContext::initPredicateCollection( + const kuzu::binder::expression_vector& predicates) { + for (auto& predicate : predicates) { + auto dependentVarNames = predicate->getDependentVariableNames(); + if (dependentVarNames.size() == 1) { + auto varName = *dependentVarNames.begin(); + if (!predicateSet->varName2Predicates.contains(varName)) { + predicateSet->varName2Predicates.insert({varName, expression_vector{}}); + } + predicateSet->varName2Predicates.at(varName).push_back(predicate); + } else { + predicateSet->multiVarPredicates.push_back(predicate); + } + } +} + SubqueryGraph JoinOrderEnumeratorContext::getFullyMatchedSubqueryGraph() const { auto subqueryGraph = SubqueryGraph(*queryGraph); for (auto i = 0u; i < queryGraph->getNumQueryNodes(); ++i) { diff --git a/src/planner/operator/logical_plan_util.cpp b/src/planner/operator/logical_plan_util.cpp index ca96a1f9b24..ca88fc7a7da 100644 --- a/src/planner/operator/logical_plan_util.cpp +++ b/src/planner/operator/logical_plan_util.cpp @@ -10,25 +10,7 @@ using namespace kuzu::binder; namespace kuzu { namespace planner { -std::shared_ptr LogicalPlanUtil::getSequentialNode(LogicalPlan& plan) { - auto pipelineSource = getCurrentPipelineSourceOperator(plan); - if (pipelineSource->getOperatorType() != LogicalOperatorType::SCAN_NODE) { - // Pipeline source is not ScanNodeID, meaning at least one sink has happened (e.g. HashJoin) - // and we loose any sequential guarantees. - return nullptr; - } - return ((LogicalScanNode*)pipelineSource)->getNode(); -} -LogicalOperator* LogicalPlanUtil::getCurrentPipelineSourceOperator(LogicalPlan& plan) { - auto op = plan.getLastOperator().get(); - // Operator with more than one child will be broken into different pipelines. - while (op->getNumChildren() == 1) { - op = op->getChild(0).get(); - } - assert(op != nullptr); - return op; -} void LogicalPlanUtil::encodeJoinRecursive( LogicalOperator* logicalOperator, std::string& encodeString) { diff --git a/src/planner/query_planner.cpp b/src/planner/query_planner.cpp index 13089c8634a..0243f02e6e6 100644 --- a/src/planner/query_planner.cpp +++ b/src/planner/query_planner.cpp @@ -183,8 +183,7 @@ void QueryPlanner::planOptionalMatch(const QueryGraphCollection& queryGraphColle // When correlated variables are all NODE IDs, the subquery can be un-nested as left join. // Join nodes are scanned twice in both outer and inner. However, we make sure inner table // scan only scans node ID and does not scan from storage (i.e. no property scan). - auto prevContext = joinOrderEnumerator.enterSubquery( - &outerPlan, expression_vector{} /* nothing to scan from outer */, joinNodeIDs); + auto prevContext = joinOrderEnumerator.enterSubquery(joinNodeIDs); auto innerPlans = joinOrderEnumerator.enumerate(queryGraphCollection, predicates); auto bestInnerPlan = getBestPlan(std::move(innerPlans)); joinOrderEnumerator.exitSubquery(std::move(prevContext)); @@ -212,8 +211,7 @@ void QueryPlanner::planRegularMatch(const QueryGraphCollection& queryGraphCollec // Multi-part query is actually CTE and CTE can be considered as a subquery but does not scan // from outer (i.e. can always be un-nest). So we plan multi-part query in the same way as // planning an un-nest subquery. - auto prevContext = joinOrderEnumerator.enterSubquery( - &prevPlan, expression_vector{} /* nothing to scan from outer */, joinNodeIDs); + auto prevContext = joinOrderEnumerator.enterSubquery(joinNodeIDs); auto plans = joinOrderEnumerator.enumerate(queryGraphCollection, predicatesToPushDown); joinOrderEnumerator.exitSubquery(std::move(prevContext)); auto bestPlan = getBestPlan(std::move(plans)); @@ -238,8 +236,7 @@ void QueryPlanner::planExistsSubquery( if (ExpressionUtil::allExpressionsHaveDataType(correlatedExpressions, INTERNAL_ID)) { auto joinNodeIDs = getJoinNodeIDs(correlatedExpressions); // Unnest as mark join. See planOptionalMatch for unnesting logic. - auto prevContext = joinOrderEnumerator.enterSubquery( - &outerPlan, expression_vector{} /* nothing to scan from outer */, joinNodeIDs); + auto prevContext = joinOrderEnumerator.enterSubquery(joinNodeIDs); auto predicates = subquery->hasWhereExpression() ? subquery->getWhereExpression()->splitOnAND() : expression_vector{};