Add full support for Optional in distributed planning

Summary:
This change should completely support planning Optional for distributed
execution. Cartesian matching is handled, as well as dependencies
between optional branch and the main input branch.

Unit tests are expanded to cover the planning algorithm.

Reviewers: msantl, mtomic

Reviewed By: msantl

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1480
This commit is contained in:
Teon Banek 2018-07-09 11:04:13 +02:00
parent 90519f0beb
commit 41358f2fac
3 changed files with 264 additions and 59 deletions

View File

@ -45,7 +45,7 @@ struct Branch {
std::shared_ptr<LogicalOperator> subtree;
// Optional parent of the subtree which needs to come after the Cartesian
// operator, because it depends on some other Cartesian branch.
// parent_start is shared_ptr, because the ownership will be transfered to
// parent_start is shared_ptr, because the ownership will be transferred to
// some other operator.
std::shared_ptr<LogicalOperator> parent_start;
// parent_end is pointer, because we may only change its input.
@ -54,6 +54,20 @@ struct Branch {
std::experimental::optional<int64_t> depends_on;
};
// Find the subtree parent, below which no operator uses symbols found in the
// `forbidden_symbols` set.
//
// Branch is returned {subtree, parent, depends_on}, where the parent may be
// nullptr if the given `op` is already a subtree which doesn't use
// `forbidden_symbols`. `depends_on` is set to the minimum index of the
// `forbidden_symbols` that the operators above the `subtree` depend on. The
// returned `parent` is therefore the last operator which depends on
// `forbidden_symbols`.
Branch FindIndependentSubtree(
const std::shared_ptr<LogicalOperator> &op,
const std::vector<std::vector<Symbol>> &forbidden_symbols,
SymbolTable *symbol_table, AstStorage *storage);
// Find the subtree parent, below which no operator uses symbols found in the
// `forbidden_symbols` set. The operator tree may be modified in cases when an
// indexed lookup is split to regular lookup + filtering.
@ -332,6 +346,19 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
return true;
}
bool PreVisit(ConstructNamedPath &op) override {
prev_ops_.push_back(&op);
return true;
}
bool PostVisit(ConstructNamedPath &op) override {
prev_ops_.pop_back();
if (branch_.subtree) return true;
if (auto found = ContainsForbidden(op.path_elements())) {
SetBranch(op.input(), &op, *found);
}
return true;
}
bool PreVisit(Filter &op) override {
prev_ops_.push_back(&op);
return true;
@ -366,7 +393,19 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
}
bool PreVisit(Optional &optional) override {
throw utils::NotYetImplemented("distributed Cartesian planning");
prev_ops_.push_back(&optional);
optional.input()->Accept(*this);
return false;
}
bool PostVisit(Optional &optional) override {
prev_ops_.pop_back();
if (branch_.subtree) return true;
auto optional_branch = FindIndependentSubtree(
optional.optional(), forbidden_symbols_, symbol_table_, storage_);
if (optional_branch.depends_on) {
SetBranch(optional.input(), &optional, *optional_branch.depends_on);
}
return true;
}
bool PreVisit(Unwind &unwind) override {
@ -663,16 +702,7 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
}
};
// Find the subtree parent, below which no operator uses symbols found in the
// `forbidden_symbols` set.
//
// Branch is returned {subtree, parent, depends_on}, where the parent may be
// nullptr if the given `op` is already a subtree which doesn't use
// `forbidden_symbols`. `depends_on` is set to the minimum index of the
// `forbidden_symbols` that the operators above the `subtree` depend on. The
// returned `parent` is therefore the last operator which depends on
// `forbidden_symbols`.
auto FindIndependentSubtree(
Branch FindIndependentSubtree(
const std::shared_ptr<LogicalOperator> &op,
const std::vector<std::vector<Symbol>> &forbidden_symbols,
SymbolTable *symbol_table, AstStorage *storage) {
@ -809,11 +839,25 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
}
void AddForCartesian(ScanAll *scan) {
if (!lhs_optional_symbols_.empty() && cartesian_symbols_.empty()) {
// If we are planning Optional, include the LHS symbols as initial
// Cartesian, because Optional behaves like a Cartesian.
cartesian_symbols_.emplace_back(lhs_optional_symbols_);
}
cartesian_branches_.emplace_back(MakeCartesianBranch(scan->input()));
// Collect modified symbols of the whole branch (independent subtree +
// parent subtree).
cartesian_symbols_.emplace_back(
scan->input()->ModifiedSymbols(distributed_plan_.symbol_table));
auto modified_symbols =
scan->input()->ModifiedSymbols(distributed_plan_.symbol_table);
if (!lhs_optional_symbols_.empty() && cartesian_symbols_.size() == 1) {
// If we are planning Optional, its LHS symbols are set as initial
// Cartesian, so we need to extend with true initial symbols.
cartesian_symbols_[0].insert(cartesian_symbols_[0].end(),
modified_symbols.begin(),
modified_symbols.end());
} else {
cartesian_symbols_.emplace_back(modified_symbols);
}
// Rewire the scan to be cut from the branch.
scan->set_input(std::make_shared<Once>());
}
@ -902,8 +946,101 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
prev_ops_.push_back(&op);
return true;
}
bool PreVisit(Optional &op) override {
prev_ops_.push_back(&op);
// Optional part will be planned separately in PostVisit.
op.input()->Accept(*this);
return false;
}
bool PostVisit(Optional &op) override {
prev_ops_.pop_back();
// Optional is kind of like a special case of Cartesian, for each LHS pull
// it will exhaust the RHS (optional part). We want to plan the RHS as if it
// was a regular stand-alone plan. For that we will create a new
// DistributedPlan and the corresponding planner. After the plan is
// complete, we need to merge it into our current plan. The behaviour is
// controlled as follows:
// 1) RHS plan doesn't have any worker plans
// - We don't care where it is executed, so we don't perform any merges.
// 2) RHS plan does have worker plans
// - We need to finish planning our worker plans and merge them
// - We preserve RHS worker plans by appending to our distributed plan.
// - Merge the independent subtree of RHS if it needs worker plan.
DistributedPlan optional_plan;
optional_plan.master_plan_id = distributed_plan_.master_plan_id;
// Use dummy produce to simulate an end of the query, thus forcing merge
// points to be planned (like Cartesian).
optional_plan.master_plan = std::make_unique<Produce>(
op.optional(), std::vector<NamedExpression *>{});
// Temporary transfer symbol table and storage
optional_plan.symbol_table = std::move(distributed_plan_.symbol_table);
optional_plan.ast_storage = std::move(distributed_plan_.ast_storage);
// Plan the optional branch
DistributedPlanner optional_planner(optional_plan, next_plan_id_);
CHECK(lhs_optional_symbols_.empty()) << "Unexpected nested Optional";
// Pass LHS symbols, so that the planner puts dependant operations after a
// merge point.
optional_planner.lhs_optional_symbols_ =
op.input()->ModifiedSymbols(optional_plan.symbol_table);
optional_plan.master_plan->Accept(optional_planner);
// Revert storage and symbol table
distributed_plan_.ast_storage = std::move(optional_plan.ast_storage);
distributed_plan_.symbol_table = std::move(optional_plan.symbol_table);
CHECK(!optional_planner.NeedsSynchronize()) << "Optional shouldn't write";
CHECK(!NeedsSynchronize()) << "Expected Synchronize before Optional";
if (optional_plan.worker_plans.empty() && !optional_planner.ShouldSplit()) {
// Case 1)
// Optional subtree doesn't create any worker plans (i.e. has no ScanAll),
// we continue as normal.
return true;
}
// Case 2)
// Since we have worker plans in optional subtree, we need to merge our
// plans on master, because Optional behaves like Cartesian.
if (!cartesian_branches_.empty()) {
Split(op, PlanCartesian(op.input()));
} else if (ShouldSplit()) {
auto input = op.input();
auto pull_id = AddWorkerPlan(input);
Split(op, std::make_shared<PullRemote>(
input, pull_id,
input->ModifiedSymbols(distributed_plan_.symbol_table)));
}
on_master_ = true;
// Add new worker plans from optional subtree
for (const auto &plan : optional_plan.worker_plans) {
distributed_plan_.worker_plans.emplace_back(plan);
}
CHECK(dynamic_cast<Produce *>(optional_plan.master_plan.get()));
if (optional_planner.on_master_) {
// This means that optional planned a Cartesian and the dependencies
// on LHS symbols should have been taken care of.
SetOnPrevious(std::make_unique<Optional>(
op.input(), optional_plan.master_plan->input(),
op.optional_symbols()));
return true;
}
CHECK(optional_planner.ShouldSplit());
// We need to plan a pull remote, but first determine the which subtree
// can be independently pulled.
auto branch = FindIndependentSubtree(
optional_plan.master_plan->input(),
{optional_planner.lhs_optional_symbols_},
&distributed_plan_.symbol_table, &distributed_plan_.ast_storage);
auto pull_id = AddWorkerPlan(branch.subtree);
// TODO: Possible optimization is to pull only needed symbols.
auto pull_remote = std::make_shared<PullRemote>(
branch.subtree, pull_id,
branch.subtree->ModifiedSymbols(distributed_plan_.symbol_table));
std::shared_ptr<LogicalOperator> new_opt = pull_remote;
if (branch.depends_on) {
branch.parent_end->set_input(pull_remote);
new_opt = branch.parent_start;
}
SetOnPrevious(
std::make_unique<Optional>(op.input(), new_opt, op.optional_symbols()));
return true;
}
@ -1238,7 +1375,7 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
}
bool PostVisit(Accumulate &acc) override {
prev_ops_.pop_back();
DCHECK(needs_synchronize_)
CHECK(needs_synchronize_)
<< "Expected Accumulate to follow a write operator";
// Create a synchronization point. Use pull remote to fetch accumulated
// symbols from workers. Accumulation is done through Synchronize, so we
@ -1405,6 +1542,9 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
// Symbols modified by the currently stored Cartesian branches. Each vector
// corresponds to the above Cartesian branch.
std::vector<std::vector<Symbol>> cartesian_symbols_;
// Symbols used in the left hand side (input) of the Optional operator. This
// is set only if we are planning the optional part separately.
std::vector<Symbol> lhs_optional_symbols_;
bool has_scan_all_ = false;
bool needs_synchronize_ = false;
bool should_split_ = false;
@ -1431,7 +1571,7 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
void SetOnPrevious(std::unique_ptr<LogicalOperator> input_op) {
auto *prev_op = prev_ops_.back();
DCHECK(prev_op)
CHECK(prev_op)
<< "SetOnPrevious should only be called when there is a previously "
"visited operation";
if (!prev_op->HasSingleInput())

View File

@ -57,7 +57,7 @@ class DistributedInterpretationTest : public DistributedGraphDbTest {
TEST_F(DistributedInterpretationTest, PullTest) {
auto results = Run("OPTIONAL MATCH(n) UNWIND(RANGE(0, 20)) AS X RETURN 1");
ASSERT_EQ(results.size(), 3 * 21);
ASSERT_EQ(results.size(), 1 * 21);
for (auto result : results) {
ASSERT_EQ(result.size(), 1U);

View File

@ -1143,8 +1143,6 @@ TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) {
auto as_p = AS("p");
QUERY(SINGLE_QUERY(OPTIONAL_MATCH(pattern), RETURN("p", as_p)));
auto symbol_table = MakeSymbolTable(*storage.query());
std::list<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(),
new ExpectConstructNamedPath()};
auto get_symbol = [&symbol_table](const auto *ast_node) {
return symbol_table.at(*ast_node->identifier_);
};
@ -1152,13 +1150,15 @@ TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) {
get_symbol(edge), get_symbol(node_m)};
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
std::list<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(),
new ExpectConstructNamedPath()};
CheckPlan(planner.plan(), symbol_table,
ExpectOptional(optional_symbols, optional), ExpectProduce());
optional.push_back(new ExpectPullRemote(optional_symbols));
auto expected = ExpectDistributed(
MakeCheckers(ExpectOptional(optional_symbols, optional), ExpectProduce(),
ExpectPullRemote({symbol_table.at(*as_p)})),
MakeCheckers(ExpectOptional(optional_symbols, optional),
ExpectProduce()));
MakeCheckers(ExpectOptional(optional_symbols, optional), ExpectProduce()),
MakeCheckers(ExpectScanAll(), ExpectExpand(),
ExpectConstructNamedPath()));
CheckDistributedPlan(planner.plan(), symbol_table, expected);
}
@ -1304,8 +1304,7 @@ TYPED_TEST(TestPlanner, MultiMatch) {
MakeCheckers(ExpectScanAll(), ExpectExpand(), ExpectExpand(),
ExpectExpandUniquenessFilter<EdgeAccessor>(), right_pull);
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectProduce()),
MakeCheckers(ExpectScanAll(), ExpectExpand()),
MakeCheckers(ExpectScanAll(), ExpectExpand(), ExpectExpand(),
ExpectExpandUniquenessFilter<EdgeAccessor>()));
@ -2647,9 +2646,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianCreateExpand) {
MakeCheckers(ExpectScanAll(),
ExpectPullRemote({symbol_table.at(*node_b->identifier_)}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectCreateExpand(), ExpectSynchronize(false),
ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectCreateExpand(),
ExpectSynchronize(false), ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
@ -2673,8 +2671,7 @@ TYPED_TEST(TestPlanner, DistributedCartesianExpand) {
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectExpand(),
ExpectPullRemote({sym_b, sym_e, sym_c}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectProduce()),
MakeCheckers(ExpectScanAll()),
MakeCheckers(ExpectScanAll(), ExpectExpand()));
FakeDbAccessor dba;
@ -2696,8 +2693,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianExpandToExisting) {
auto sym_b = symbol_table.at(*node_b->identifier_);
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectExpand(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectExpand(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
@ -2718,8 +2715,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianExpandFromExisting) {
auto sym_b = symbol_table.at(*node_b->identifier_);
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectExpand(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectExpand(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
@ -2746,12 +2743,10 @@ TYPED_TEST(TestPlanner, DistributedCartesianFilter) {
auto mid_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_c}));
auto mid_right_cart =
MakeCheckers(ExpectCartesian(std::move(mid_cart), std::move(right_cart)),
ExpectFilter());
MakeCheckers(ExpectCartesian(mid_cart, right_cart), ExpectFilter());
auto expected = ExpectDistributed(
MakeCheckers(
ExpectCartesian(std::move(left_cart), std::move(mid_right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, mid_right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll(), ExpectFilter()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
@ -2782,8 +2777,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianIndexedScanByProperty) {
auto right_cart =
MakeCheckers(ExpectScanAllByLabel(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAllByLabel()));
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
@ -2812,8 +2807,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianIndexedScanByLowerBound) {
auto right_cart =
MakeCheckers(ExpectScanAllByLabel(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAllByLabel()));
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
@ -2842,8 +2837,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianIndexedScanByUpperBound) {
auto right_cart =
MakeCheckers(ExpectScanAllByLabel(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAllByLabel()));
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
@ -2881,8 +2876,8 @@ TEST(TestPlanner, DistributedCartesianIndexedScanByBothBounds) {
auto right_cart =
MakeCheckers(ExpectScanAllByLabel(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAllByLabel()));
CheckDistributedPlan(*produce, symbol_table, expected);
}
@ -2920,8 +2915,8 @@ TEST(TestPlanner, DistributedCartesianIndexedScanByLowerWithBothBounds) {
label, prop, lower_bound, std::experimental::nullopt),
ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()),
MakeCheckers(ExpectScanAllByLabelPropertyRange(
label, prop, lower_bound, std::experimental::nullopt)));
@ -2961,8 +2956,8 @@ TEST(TestPlanner, DistributedCartesianIndexedScanByUpperWithBothBounds) {
label, prop, std::experimental::nullopt, upper_bound),
ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectFilter(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()),
MakeCheckers(ExpectScanAllByLabelPropertyRange(
label, prop, std::experimental::nullopt, upper_bound)));
@ -2982,11 +2977,11 @@ TYPED_TEST(TestPlanner, DistributedCartesianProduce) {
MakeCheckers(ExpectScanAll(), ExpectProduce(), ExpectPullRemote({sym_a}));
auto sym_b = symbol_table.at(*node_b->identifier_);
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectScanAll(), ExpectProduce()),
MakeCheckers(ExpectScanAll()));
auto expected =
ExpectDistributed(MakeCheckers(ExpectCartesian(left_cart, right_cart),
ExpectFilter(), ExpectProduce()),
MakeCheckers(ExpectScanAll(), ExpectProduce()),
MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
@ -3005,8 +3000,8 @@ TYPED_TEST(TestPlanner, DistributedCartesianUnwind) {
auto sym_b = symbol_table.at(*node_b->identifier_);
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
ExpectUnwind(), ExpectProduce()),
MakeCheckers(ExpectCartesian(left_cart, right_cart), ExpectUnwind(),
ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
@ -3029,7 +3024,7 @@ TYPED_TEST(TestPlanner, DistributedCartesianCreateNode) {
auto sym_c = symbol_table.at(*node_c->identifier_);
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_c}));
auto expected = ExpectDistributed(
MakeCheckers(ExpectCartesian(std::move(left_cart), std::move(right_cart)),
MakeCheckers(ExpectCartesian(left_cart, right_cart),
ExpectCreateNode(true), ExpectSynchronize(false)),
MakeCheckers(ExpectScanAll(), ExpectCreateNode()),
MakeCheckers(ExpectScanAll()));
@ -3038,6 +3033,76 @@ TYPED_TEST(TestPlanner, DistributedCartesianCreateNode) {
CheckDistributedPlan(planner.plan(), symbol_table, expected);
}
TYPED_TEST(TestPlanner, DistributedOptionalExpand) {
// Test MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN e;
AstStorage storage;
auto *node_n = NODE("n");
auto *edge_e = EDGE("e");
auto *node_m = NODE("m");
auto *ret_e = RETURN("e");
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)),
OPTIONAL_MATCH(PATTERN(node_n, edge_e, node_m)), ret_e));
auto symbol_table = MakeSymbolTable(*storage.query());
auto sym_e = symbol_table.at(*ret_e->body_.named_expressions[0]);
std::list<BaseOpChecker *> optional{new ExpectExpand()};
auto expected = ExpectDistributed(
MakeCheckers(ExpectScanAll(), ExpectOptional(optional), ExpectProduce(),
ExpectPullRemote({sym_e})),
MakeCheckers(ExpectScanAll(), ExpectOptional(optional), ExpectProduce()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
}
TYPED_TEST(TestPlanner, DistributedOptionalCartesian) {
// Test MATCH (a) OPTIONAL MATCH (b), (c) WHERE b > a RETURN c;
AstStorage storage;
auto *node_a = NODE("a");
auto *node_b = NODE("b");
auto *node_c = NODE("c");
QUERY(SINGLE_QUERY(
MATCH(PATTERN(node_a)), OPTIONAL_MATCH(PATTERN(node_b), PATTERN(node_c)),
WHERE(GREATER(node_b->identifier_, node_a->identifier_)), RETURN("c")));
auto symbol_table = MakeSymbolTable(*storage.query());
auto sym_a = symbol_table.at(*node_a->identifier_);
auto sym_b = symbol_table.at(*node_b->identifier_);
auto sym_c = symbol_table.at(*node_c->identifier_);
auto left_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_b}));
auto right_cart = MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_c}));
std::list<BaseOpChecker *> optional{
new ExpectCartesian(left_cart, right_cart), new ExpectFilter()};
auto expected = ExpectDistributed(
MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_a}),
ExpectOptional(optional), ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()),
MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
}
TYPED_TEST(TestPlanner, DistributedOptionalScanExpandExisting) {
// Test MATCH (a) OPTIONAL MATCH (b)-[e]-(a) RETURN e;
AstStorage storage;
auto *node_a = NODE("a");
auto *node_b = NODE("b");
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_a)),
OPTIONAL_MATCH(PATTERN(node_b, EDGE("e"), NODE("a"))),
RETURN("e")));
auto symbol_table = MakeSymbolTable(*storage.query());
auto sym_a = symbol_table.at(*node_a->identifier_);
auto sym_b = symbol_table.at(*node_b->identifier_);
std::list<BaseOpChecker *> optional{
new ExpectScanAll(), new ExpectPullRemote({sym_b}), new ExpectExpand()};
auto expected = ExpectDistributed(
MakeCheckers(ExpectScanAll(), ExpectPullRemote({sym_a}),
ExpectOptional(optional), ExpectProduce()),
MakeCheckers(ExpectScanAll()), MakeCheckers(ExpectScanAll()));
FakeDbAccessor dba;
auto planner = MakePlanner<TypeParam>(dba, storage, symbol_table);
CheckDistributedPlan(planner.plan(), symbol_table, expected);
}
TEST(CapnpSerial, Union) {
std::vector<Symbol> left_symbols{
Symbol("symbol", 1, true, Symbol::Type::Edge)};