diff --git a/src/planner/match/LabelIndexSeek.cpp b/src/planner/match/LabelIndexSeek.cpp index 47f8b8392..5c286809a 100644 --- a/src/planner/match/LabelIndexSeek.cpp +++ b/src/planner/match/LabelIndexSeek.cpp @@ -85,6 +85,53 @@ StatusOr LabelIndexSeek::transformNode(NodeContext* nodeCtx) { plan.tail = scan; plan.root = scan; + // This if-block is a patch for or-filter-embeding to avoid OOM, + // and it should be converted to an `optRule` after the match validator is refactored + auto& whereCtx = matchClauseCtx->where; + if (whereCtx && whereCtx->filter) { + auto* filter = whereCtx->filter; + const auto nodeAlias = *nodeCtx->info->alias; + auto* objPool = matchClauseCtx->qctx->objPool(); + if (filter->kind() == Expression::Kind::kLogicalOr) { + auto labelExprs = ExpressionUtils::collectAll(filter, {Expression::Kind::kLabel}); + bool labelMatched = true; + for (auto* labelExpr : labelExprs) { + DCHECK_EQ(labelExpr->kind(), Expression::Kind::kLabel); + if (*(static_cast(labelExpr)->name()) != nodeAlias) { + labelMatched = false; + break; + } + } + if (labelMatched) { + auto* flattenFilter = ExpressionUtils::flattenInnerLogicalExpr(filter); + DCHECK_EQ(flattenFilter->kind(), Expression::Kind::kLogicalOr); + auto& filterItems = static_cast(flattenFilter)->operands(); + auto canBeEmbeded = [](Expression::Kind k) -> bool { + return k == Expression::Kind::kRelEQ || k == Expression::Kind::kRelLT || + k == Expression::Kind::kRelLE || k == Expression::Kind::kRelGT || + k == Expression::Kind::kRelGE; + }; + bool canBeEmbeded2IndexScan = true; + for (auto& f : filterItems) { + if (!canBeEmbeded(f->kind())) { + canBeEmbeded2IndexScan = false; + } + } + if (canBeEmbeded2IndexScan) { + auto* srcFilter = + objPool->add(ExpressionUtils::rewriteLabelAttr2TagProp(flattenFilter)); + delete (flattenFilter); + storage::cpp2::IndexQueryContext ctx; + ctx.set_filter(Expression::encode(*srcFilter)); + auto context = + std::make_unique>(); + context->emplace_back(std::move(ctx)); + scan->setIndexQueryContext(std::move(context)); + whereCtx.reset(); + } + } + } + } // initialize start expression in project node nodeCtx->initialExpr.reset(ExpressionUtils::newVarPropExpr(kVid)); return plan; diff --git a/src/util/ExpressionUtils.cpp b/src/util/ExpressionUtils.cpp index 0379bc9b2..c1d0c62e0 100644 --- a/src/util/ExpressionUtils.cpp +++ b/src/util/ExpressionUtils.cpp @@ -106,6 +106,38 @@ ExpressionUtils::pullOrsImpl(LogicalExpression *expr, } } +Expression *ExpressionUtils::flattenInnerLogicalAndExpr(const Expression *expr) { + auto matcher = [](const Expression *e) -> bool { + return e->kind() == Expression::Kind::kLogicalAnd; + }; + auto rewriter = [](const Expression *e) -> Expression * { + pullAnds(const_cast(e)); + return e->clone().release(); + }; + + return RewriteVisitor::transform(expr, std::move(matcher), std::move(rewriter)); +} + +Expression *ExpressionUtils::flattenInnerLogicalOrExpr(const Expression *expr) { + auto matcher = [](const Expression *e) -> bool { + return e->kind() == Expression::Kind::kLogicalOr; + }; + auto rewriter = [](const Expression *e) -> Expression * { + pullOrs(const_cast(e)); + return e->clone().release(); + }; + + return RewriteVisitor::transform(expr, std::move(matcher), std::move(rewriter)); +} + +Expression *ExpressionUtils::flattenInnerLogicalExpr(const Expression *expr) { + auto* andFlattenExpr = flattenInnerLogicalAndExpr(expr); + auto* allFlattenExpr = flattenInnerLogicalOrExpr(andFlattenExpr); + delete(andFlattenExpr); + + return allFlattenExpr; +} + VariablePropertyExpression *ExpressionUtils::newVarPropExpr(const std::string &prop, const std::string &var) { return new VariablePropertyExpression(new std::string(var), new std::string(prop)); diff --git a/src/util/ExpressionUtils.h b/src/util/ExpressionUtils.h index d8569eb7c..a073d9f96 100644 --- a/src/util/ExpressionUtils.h +++ b/src/util/ExpressionUtils.h @@ -218,6 +218,10 @@ class ExpressionUtils { Expression::Kind kind, const std::vector>& rels); + static Expression* flattenInnerLogicalAndExpr(const Expression* expr); + static Expression* flattenInnerLogicalOrExpr(const Expression* expr); + static Expression* flattenInnerLogicalExpr(const Expression* expr); + static std::unique_ptr expandExpr(const Expression* expr); static std::unique_ptr expandImplAnd(const Expression* expr); diff --git a/src/util/test/ExpressionUtilsTest.cpp b/src/util/test/ExpressionUtilsTest.cpp index 75b202db1..e6e0696bb 100644 --- a/src/util/test/ExpressionUtilsTest.cpp +++ b/src/util/test/ExpressionUtilsTest.cpp @@ -401,6 +401,114 @@ TEST_F(ExpressionUtilsTest, pushAnds) { ASSERT_EQ(expected, t->toString()); } +TEST_F(ExpressionUtilsTest, flattenInnerLogicalExpr) { + using Kind = Expression::Kind; + // true AND false AND true + { + auto *first = new ConstantExpression(true); + auto *second = new ConstantExpression(false); + auto *third = new ConstantExpression(true); + LogicalExpression expr(Kind::kLogicalAnd, + new LogicalExpression(Kind::kLogicalAnd, + first, + second), + third); + LogicalExpression expected(Kind::kLogicalAnd); + expected.addOperand(first->clone().release()); + expected.addOperand(second->clone().release()); + expected.addOperand(third->clone().release()); + auto* newExpr = ExpressionUtils::flattenInnerLogicalExpr(&expr); + ASSERT_EQ(expected, *newExpr); + delete(newExpr); + } + // true OR false OR true + { + auto *first = new ConstantExpression(true); + auto *second = new ConstantExpression(false); + auto *third = new ConstantExpression(true); + LogicalExpression expr(Kind::kLogicalOr, + new LogicalExpression(Kind::kLogicalOr, + first, + second), + third); + LogicalExpression expected(Kind::kLogicalOr); + expected.addOperand(first->clone().release()); + expected.addOperand(second->clone().release()); + expected.addOperand(third->clone().release()); + auto* newExpr = ExpressionUtils::flattenInnerLogicalExpr(&expr); + ASSERT_EQ(expected, *newExpr); + delete(newExpr); + } + // (true OR false OR true)==(true AND false AND true) + { + auto *or1 = new ConstantExpression(true); + auto *or2 = new ConstantExpression(false); + auto *or3 = new ConstantExpression(true); + auto* logicOrExpr = new LogicalExpression(Kind::kLogicalOr, + new LogicalExpression(Kind::kLogicalOr, + or1, + or2), + or3); + auto *and1 = new ConstantExpression(false); + auto *and2 = new ConstantExpression(false); + auto *and3 = new ConstantExpression(true); + auto* logicAndExpr = new LogicalExpression(Kind::kLogicalAnd, + new LogicalExpression(Kind::kLogicalAnd, + and1, + and2), + and3); + RelationalExpression expr(Kind::kRelEQ, logicOrExpr, logicAndExpr); + + auto* logicOrFlatten = new LogicalExpression(Kind::kLogicalOr); + logicOrFlatten->addOperand(or1->clone().release()); + logicOrFlatten->addOperand(or2->clone().release()); + logicOrFlatten->addOperand(or3->clone().release()); + auto* logicAndFlatten = new LogicalExpression(Kind::kLogicalAnd); + logicAndFlatten->addOperand(and1->clone().release()); + logicAndFlatten->addOperand(and2->clone().release()); + logicAndFlatten->addOperand(and3->clone().release()); + RelationalExpression expected(Kind::kRelEQ, logicOrFlatten, logicAndFlatten); + + auto* newExpr = ExpressionUtils::flattenInnerLogicalExpr(&expr); + ASSERT_EQ(expected, *newExpr); + delete(newExpr); + } + // (true OR false OR true) AND (true AND false AND true) + { + auto *or1 = new ConstantExpression(true); + auto *or2 = new ConstantExpression(false); + auto *or3 = new ConstantExpression(true); + auto* logicOrExpr = new LogicalExpression(Kind::kLogicalOr, + new LogicalExpression(Kind::kLogicalOr, + or1, + or2), + or3); + auto *and1 = new ConstantExpression(false); + auto *and2 = new ConstantExpression(false); + auto *and3 = new ConstantExpression(true); + auto* logicAndExpr = new LogicalExpression(Kind::kLogicalAnd, + new LogicalExpression(Kind::kLogicalAnd, + and1, + and2), + and3); + LogicalExpression expr(Kind::kLogicalAnd, logicOrExpr, logicAndExpr); + + auto* logicOrFlatten = new LogicalExpression(Kind::kLogicalOr); + logicOrFlatten->addOperand(or1->clone().release()); + logicOrFlatten->addOperand(or2->clone().release()); + logicOrFlatten->addOperand(or3->clone().release()); + LogicalExpression expected(Kind::kLogicalAnd); + expected.addOperand(logicOrFlatten); + expected.addOperand(and1->clone().release()); + expected.addOperand(and2->clone().release()); + expected.addOperand(and3->clone().release()); + + auto* newExpr = ExpressionUtils::flattenInnerLogicalExpr(&expr); + ASSERT_EQ(expected, *newExpr); + delete(newExpr); + } +} + std::unique_ptr parse(const std::string& expr) { std::string query = "LOOKUP on t1 WHERE " + expr; GQLParser parser; diff --git a/tests/common/plan_differ.py b/tests/common/plan_differ.py index 0c4bdf10c..1d722a40b 100644 --- a/tests/common/plan_differ.py +++ b/tests/common/plan_differ.py @@ -91,12 +91,12 @@ def _check_op_info(self, exp, resp): if resp is None: if exp: return f"expect: {exp} but resp plan node is None" - else: + if exp: resp_dict = { f"{bytes.decode(pair.key)}": f"{bytes.decode(pair.value)}" for pair in resp } - if exp and not self._is_subdict_nested(exp, resp_dict): + if not self._is_subdict_nested(exp, resp_dict): return "Invalid descriptions, expect: {} vs. resp: {}".format( json.dumps(exp), json.dumps(resp_dict)) return None diff --git a/tests/tck/features/optimizer/IndexScanRule.feature b/tests/tck/features/optimizer/IndexScanRule.feature new file mode 100644 index 000000000..b1de86ccc --- /dev/null +++ b/tests/tck/features/optimizer/IndexScanRule.feature @@ -0,0 +1,208 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. +Feature: Match index selection + + Background: + Given a graph with space named "nba" + + Scenario: and filter embeding + When profiling query: + """ + MATCH (v:player) + WHERE v.name>"Tim Duncan" and v.name<="Yao Ming" + RETURN v + """ + Then the result should be, in any order: + | v | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | + And the execution plan should be: + | id | name | dependencies | operator info | + | 10 | Project | 13 | | + | 13 | Filter | 7 | | + | 7 | Project | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 15 | | + | 15 | GetVertices | 11 | | + | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE","column":"name","beginValue":"\"Tim Duncan","endValue":"\"Yao Ming"}}} | + | 0 | Start | | | + + Scenario: or filter embeding + When profiling query: + """ + MATCH (v:player) + WHERE + v.name<="Aron Baynes" + or v.name>"Yao Ming" + or v.name=="Kobe Bryant" + or v.age>40 + RETURN v + """ + Then the result should be, in any order: + | v | + | ("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"}) | + | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | + | ("Amar'e Stoudemire" :player{age: 36, name: "Amar'e Stoudemire"}) | + | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | + | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | + And the execution plan should be: + | id | name | dependencies | operator info | + | 10 | Project | 13 | | + | 13 | Filter | 7 | {"condition":"!(hasSameEdgeInPath($-.__COL_0))"} | + | 7 | Project | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 15 | | + | 15 | GetVertices | 11 | | + | 11 | IndexScan | 0 | | + | 0 | Start | | | + + Scenario: degenerate to full tag scan + When profiling query: + """ + MATCH (v:player)-[:like]->(n) + WHERE + v.name<="Aron Baynes" + or n.age>45 + RETURN v, n + """ + Then the result should be, in any order: + | v | n | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | + | ("Amar'e Stoudemire" :player{age: 36, name: "Amar'e Stoudemire"}) | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | + | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + And the execution plan should be: + | id | name | dependencies | operator info | + | 16 | Project | 19 | | + | 19 | Filter | 13 | { "condition": "((($v.name<=\"Aron Baynes\") OR ($n.age>45)) AND !(hasSameEdgeInPath($-.__COL_0)))"} | + | 13 | Project | 12 | | + | 12 | InnerJoin | 11 | | + | 11 | Project | 21 | | + | 21 | GetVertices | 7 | | + | 7 | Filter | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 23 | | + | 23 | GetNeighbors | 17 | | + | 17 | IndexScan | 0 | | + | 0 | Start | | | + # This is actually the optimization for another optRule, + # but it is necessary to ensure that the current optimization does not destroy this scenario + # and it can be considered in the subsequent refactoring + When profiling query: + """ + MATCH (v:player)-[:like]->(n) + WHERE + v.name<="Aron Baynes" + or v.age>45 + or true + or v.age+1 + or v.name + RETURN v, n + """ + Then the result should be, in any order: + | v | n | + | ("Luka Doncic" :player{age: 20, name: "Luka Doncic"}) | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | + | ("Luka Doncic" :player{age: 20, name: "Luka Doncic"}) | ("James Harden" :player{age: 29, name: "James Harden"}) | + | ("Luka Doncic" :player{age: 20, name: "Luka Doncic"}) | ("Kristaps Porzingis" :player{age: 23, name: "Kristaps Porzingis"}) | + | ("Klay Thompson" :player{age: 29, name: "Klay Thompson"}) | ("Stephen Curry" :player{age: 31, name: "Stephen Curry"}) | + | ("Joel Embiid" :player{age: 25, name: "Joel Embiid"}) | ("Ben Simmons" :player{age: 22, name: "Ben Simmons"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | + | ("Kyrie Irving" :player{age: 26, name: "Kyrie Irving"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | + | ("Marc Gasol" :player{age: 34, name: "Marc Gasol"}) | ("Paul Gasol" :player{age: 38, name: "Paul Gasol"}) | + | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | + | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | + | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("Blake Griffin" :player{age: 30, name: "Blake Griffin"}) | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | + | ("Ben Simmons" :player{age: 22, name: "Ben Simmons"}) | ("Joel Embiid" :player{age: 25, name: "Joel Embiid"}) | + | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | ("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"}) | + | ("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"}) | ("James Harden" :player{age: 29, name: "James Harden"}) | + | ("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"}) | ("Paul George" :player{age: 28, name: "Paul George"}) | + | ("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}) | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | + | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | + | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("James Harden" :player{age: 29, name: "James Harden"}) | ("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"}) | + | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | + | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | + | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("Paul Gasol" :player{age: 38, name: "Paul Gasol"}) | ("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"}) | + | ("Paul Gasol" :player{age: 38, name: "Paul Gasol"}) | ("Marc Gasol" :player{age: 34, name: "Marc Gasol"}) | + | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | ("Danny Green" :player{age: 31, name: "Danny Green"}) | + | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Danny Green" :player{age: 31, name: "Danny Green"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("James Harden" :player{age: 29, name: "James Harden"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Kevin Durant" :player{age: 30, name: "Kevin Durant"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Kyle Anderson" :player{age: 25, name: "Kyle Anderson"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Boris Diaw" :player{age: 36, name: "Boris Diaw"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Boris Diaw" :player{age: 36, name: "Boris Diaw"}) | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Damian Lillard" :player{age: 28, name: "Damian Lillard"}) | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | + | ("Amar'e Stoudemire" :player{age: 36, name: "Amar'e Stoudemire"}) | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("LeBron James" :player{age: 34, name: "LeBron James"}) | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | + | ("Danny Green" :player{age: 31, name: "Danny Green"}) | ("LeBron James" :player{age: 34, name: "LeBron James"}) | + | ("Danny Green" :player{age: 31, name: "Danny Green"}) | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | + | ("Danny Green" :player{age: 31, name: "Danny Green"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Paul George" :player{age: 28, name: "Paul George"}) | ("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"}) | + | ("Rudy Gay" :player{age: 32, name: "Rudy Gay"}) | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | + | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | ("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"}) | + | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | + | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | ("JaVale McGee" :player{age: 31, name: "JaVale McGee"}) | + | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Kristaps Porzingis" :player{age: 23, name: "Kristaps Porzingis"}) | ("Luka Doncic" :player{age: 20, name: "Luka Doncic"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | ("Amar'e Stoudemire" :player{age: 36, name: "Amar'e Stoudemire"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | ("Dirk Nowitzki" :player{age: 40, name: "Dirk Nowitzki"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | + | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | ("Stephen Curry" :player{age: 31, name: "Stephen Curry"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"}) | ("Ray Allen" :player{age: 43, name: "Ray Allen"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | ("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | ("Grant Hill" :player{age: 46, name: "Grant Hill"}) | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | ("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"}) | + | ("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"}) | ("Rudy Gay" :player{age: 32, name: "Rudy Gay"}) | + And the execution plan should be: + | id | name | dependencies | operator info | + | 16 | Project | 19 | | + | 19 | Filter | 13 | | + | 13 | Project | 12 | | + | 12 | InnerJoin | 11 | | + | 11 | Project | 21 | | + | 21 | GetVertices | 7 | | + | 7 | Filter | 6 | | + | 6 | Project | 5 | | + | 5 | Filter | 23 | | + | 23 | GetNeighbors | 17 | | + | 17 | IndexScan | 0 | | + | 0 | Start | | |