Implement Cartesian Cursor

Summary:
Pulls left op cursor and keeps the result, and then for each pull of
the right op cursor, adds all the left op results to produce a cartesian
product.

Reviewers: teon.banek, florijan

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1201
This commit is contained in:
Matija Santl 2018-02-15 10:20:43 +01:00
parent 763bdaac0f
commit 84e7aec27b
3 changed files with 222 additions and 9 deletions

View File

@ -328,10 +328,10 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
context.symbol_table_, db, graph_view_);
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
return db.Vertices(label_, property_, convert(lower_bound()),
convert(upper_bound()), graph_view_ == GraphView::NEW);
};
@ -1058,9 +1058,8 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
self_.graph_view_);
// For the given (vertex, edge, vertex) tuple checks if they satisfy the
// "where" condition. if so, places them in the priority queue.
auto expand_pair = [this, &evaluator, &frame](VertexAccessor from,
EdgeAccessor edge,
VertexAccessor vertex) {
auto expand_pair = [this, &evaluator, &frame](
VertexAccessor from, EdgeAccessor edge, VertexAccessor vertex) {
SwitchAccessor(edge, self_.graph_view_);
SwitchAccessor(vertex, self_.graph_view_);
@ -3026,6 +3025,86 @@ class SynchronizeCursor : public Cursor {
}
}
};
class CartesianCursor : public Cursor {
public:
CartesianCursor(const Cartesian &self, database::GraphDbAccessor &db)
: self_(self),
left_op_cursor_(self.left_op()->MakeCursor(db)),
right_op_cursor_(self_.right_op()->MakeCursor(db)) {
CHECK(left_op_cursor_ != nullptr)
<< "CartesianCursor: Missing left operator cursor.";
CHECK(right_op_cursor_ != nullptr)
<< "CartesianCursor: Missing right operator cursor.";
}
bool Pull(Frame &frame, Context &context) override {
auto copy_frame = [&frame]() {
std::vector<TypedValue> result;
for (auto &elem : frame.elems()) {
result.emplace_back(std::move(elem));
}
return result;
};
if (!cartesian_pull_initialized_) {
// Pull all left_op frames.
while (left_op_cursor_->Pull(frame, context)) {
left_op_frames_.emplace_back(copy_frame());
}
// We're setting the iterator to 'end' here so it pulls the right cursor.
left_op_frames_it_ = left_op_frames_.end();
cartesian_pull_initialized_ = true;
}
// If left operator yielded zero results there is no cartesian product.
if (left_op_frames_.empty()) {
return false;
}
auto restore_frame = [&frame](const std::vector<Symbol> &symbols,
const std::vector<TypedValue> &restore_from) {
for (const auto &symbol : symbols) {
frame[symbol] = restore_from[symbol.position()];
}
};
if (left_op_frames_it_ == left_op_frames_.end()) {
// Advance right_op_cursor_.
if (!right_op_cursor_->Pull(frame, context)) return false;
right_op_frame_ = copy_frame();
left_op_frames_it_ = left_op_frames_.begin();
} else {
// Make sure right_op_cursor last pulled results are on frame.
restore_frame(self_.right_symbols(), right_op_frame_);
}
restore_frame(self_.left_symbols(), *left_op_frames_it_);
left_op_frames_it_++;
return true;
}
void Reset() override {
left_op_cursor_->Reset();
right_op_cursor_->Reset();
right_op_frame_.clear();
left_op_frames_.clear();
left_op_frames_it_ = left_op_frames_.end();
cartesian_pull_initialized_ = false;
}
private:
const Cartesian &self_;
std::vector<std::vector<TypedValue>> left_op_frames_;
std::vector<TypedValue> right_op_frame_;
const std::unique_ptr<Cursor> left_op_cursor_;
const std::unique_ptr<Cursor> right_op_cursor_;
std::vector<std::vector<TypedValue>>::iterator left_op_frames_it_;
bool cartesian_pull_initialized_{false};
};
} // namespace
std::unique_ptr<Cursor> Synchronize::MakeCursor(
@ -3042,8 +3121,7 @@ bool Cartesian::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
std::unique_ptr<Cursor> Cartesian::MakeCursor(
database::GraphDbAccessor &db) const {
// TODO: Implement cursor.
return nullptr;
return std::make_unique<CartesianCursor>(*this, db);
}
} // namespace query::plan

View File

@ -2402,7 +2402,9 @@ class Cartesian : public LogicalOperator {
database::GraphDbAccessor &db) const override;
auto left_op() const { return left_op_; }
auto left_symbols() const { return left_symbols_; }
auto right_op() const { return right_op_; }
auto right_symbols() const { return right_symbols_; }
private:
std::shared_ptr<LogicalOperator> left_op_;

View File

@ -246,6 +246,139 @@ TEST(QueryPlan, NodeFilterMultipleLabels) {
EXPECT_EQ(results.size(), 2);
}
TEST(QueryPlan, Cartesian) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
auto add_vertex = [&dba](std::string label) {
auto vertex = dba.InsertVertex();
vertex.add_label(dba.Label(label));
return vertex;
};
std::vector<VertexAccessor> vertices{add_vertex("v1"), add_vertex("v2"),
add_vertex("v3")};
dba.AdvanceCommand();
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n");
auto m = MakeScanAll(storage, symbol_table, "m");
auto return_n = NEXPR("n", IDENT("n"));
symbol_table[*return_n->expression_] = n.sym_;
symbol_table[*return_n] =
symbol_table.CreateSymbol("named_expression_1", true);
auto return_m = NEXPR("m", IDENT("m"));
symbol_table[*return_m->expression_] = m.sym_;
symbol_table[*return_m] =
symbol_table.CreateSymbol("named_expression_2", true);
std::vector<Symbol> left_symbols{n.sym_};
std::vector<Symbol> right_symbols{m.sym_};
auto cartesian_op =
std::make_shared<Cartesian>(n.op_, left_symbols, m.op_, right_symbols);
auto produce = MakeProduce(cartesian_op, return_n, return_m);
auto results = CollectProduce(produce.get(), symbol_table, dba);
EXPECT_EQ(results.size(), 9);
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
EXPECT_EQ(results[3 * i + j][0].Value<VertexAccessor>(), vertices[j]);
EXPECT_EQ(results[3 * i + j][1].Value<VertexAccessor>(), vertices[i]);
}
}
}
TEST(QueryPlan, CartesianEmptySet) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n");
auto m = MakeScanAll(storage, symbol_table, "m");
auto return_n = NEXPR("n", IDENT("n"));
symbol_table[*return_n->expression_] = n.sym_;
symbol_table[*return_n] =
symbol_table.CreateSymbol("named_expression_1", true);
auto return_m = NEXPR("m", IDENT("m"));
symbol_table[*return_m->expression_] = m.sym_;
symbol_table[*return_m] =
symbol_table.CreateSymbol("named_expression_2", true);
std::vector<Symbol> left_symbols{n.sym_};
std::vector<Symbol> right_symbols{m.sym_};
auto cartesian_op =
std::make_shared<Cartesian>(n.op_, left_symbols, m.op_, right_symbols);
auto produce = MakeProduce(cartesian_op, return_n, return_m);
auto results = CollectProduce(produce.get(), symbol_table, dba);
EXPECT_EQ(results.size(), 0);
}
TEST(QueryPlan, CartesianThreeWay) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
auto add_vertex = [&dba](std::string label) {
auto vertex = dba.InsertVertex();
vertex.add_label(dba.Label(label));
return vertex;
};
std::vector<VertexAccessor> vertices{add_vertex("v1"), add_vertex("v2"),
add_vertex("v3")};
dba.AdvanceCommand();
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n");
auto m = MakeScanAll(storage, symbol_table, "m");
auto l = MakeScanAll(storage, symbol_table, "l");
auto return_n = NEXPR("n", IDENT("n"));
symbol_table[*return_n->expression_] = n.sym_;
symbol_table[*return_n] =
symbol_table.CreateSymbol("named_expression_1", true);
auto return_m = NEXPR("m", IDENT("m"));
symbol_table[*return_m->expression_] = m.sym_;
symbol_table[*return_m] =
symbol_table.CreateSymbol("named_expression_2", true);
auto return_l = NEXPR("l", IDENT("l"));
symbol_table[*return_l->expression_] = l.sym_;
symbol_table[*return_l] =
symbol_table.CreateSymbol("named_expression_3", true);
std::vector<Symbol> n_symbols{n.sym_};
std::vector<Symbol> m_symbols{m.sym_};
std::vector<Symbol> n_m_symbols{n.sym_, m.sym_};
std::vector<Symbol> l_symbols{l.sym_};
auto cartesian_op_1 =
std::make_shared<Cartesian>(n.op_, n_symbols, m.op_, m_symbols);
auto cartesian_op_2 = std::make_shared<Cartesian>(cartesian_op_1, n_m_symbols,
l.op_, l_symbols);
auto produce = MakeProduce(cartesian_op_2, return_n, return_m, return_l);
auto results = CollectProduce(produce.get(), symbol_table, dba);
EXPECT_EQ(results.size(), 27);
int id = 0;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 3; ++k) {
EXPECT_EQ(results[id][0].Value<VertexAccessor>(), vertices[k]);
EXPECT_EQ(results[id][1].Value<VertexAccessor>(), vertices[j]);
EXPECT_EQ(results[id][2].Value<VertexAccessor>(), vertices[i]);
++id;
}
}
}
}
class ExpandFixture : public testing::Test {
protected:
database::SingleNode db_;