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:
parent
763bdaac0f
commit
84e7aec27b
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
Loading…
Reference in New Issue
Block a user