Files
memgraph/src/query/frontend/logical/operator.hpp
florijan 981f93d575 Query - ExpandCreate, prop tests, MATCH_CREATE
Summary: Query - ExpandCreate op added (with tests). LogicalOp tests have property enabled. MATCH-CREATE test started but disabled due to MVCC.

Reviewers: buda, mislav.bradac, teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D150
2017-03-22 13:41:21 +01:00

618 lines
20 KiB
C++

#pragma once
#include <memory>
#include <sstream>
#include <vector>
#include "database/graph_db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/interpret/interpret.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "utils/visitor/visitable.hpp"
#include "utils/visitor/visitor.hpp"
namespace query {
class Cursor {
public:
virtual bool Pull(Frame&, SymbolTable&) = 0;
virtual ~Cursor() {}
};
class CreateOp;
class ScanAll;
class Expand;
class NodeFilter;
class EdgeFilter;
class Produce;
using LogicalOperatorVisitor =
::utils::Visitor<CreateOp, ScanAll, Expand, NodeFilter, EdgeFilter, Produce>;
class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
public:
auto children() { return children_; };
virtual std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) = 0;
virtual ~LogicalOperator() {}
protected:
std::vector<std::shared_ptr<LogicalOperator>> children_;
};
class CreateOp : public LogicalOperator {
// TODO add an optional input that (if given) gets pulled
// and the create op gets executed for each success
// TODO rename to CreateSingle or CreateNode
public:
CreateOp(NodeAtom* node_atom) : node_atom_(node_atom) {}
DEFVISITABLE(LogicalOperatorVisitor);
private:
class CreateOpCursor : public Cursor {
public:
CreateOpCursor(CreateOp& self, GraphDbAccessor& db)
: self_(self), db_(db) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
if (!did_create_) {
auto new_node = db_.insert_vertex();
for (auto label : self_.node_atom_->labels_) new_node.add_label(label);
ExpressionEvaluator evaluator(frame, symbol_table);
for (auto& kv : self_.node_atom_->properties_) {
kv.second->Accept(evaluator);
new_node.PropsSet(kv.first, evaluator.PopBack());
}
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
did_create_ = true;
return true;
} else
return false;
}
private:
CreateOp& self_;
GraphDbAccessor& db_;
bool did_create_{false};
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<CreateOpCursor>(*this, db);
}
private:
NodeAtom* node_atom_ = nullptr;
};
class CreateExpand : public LogicalOperator {
public:
CreateExpand(NodeAtom* node_atom, EdgeAtom* edge_atom,
const std::shared_ptr<LogicalOperator>& input,
const Symbol& input_symbol, bool node_existing)
: node_atom_(node_atom),
edge_atom_(edge_atom),
input_(input),
input_symbol_(input_symbol),
node_existing_(node_existing) {}
private:
class CreateExpandCursor : public Cursor {
public:
CreateExpandCursor(CreateExpand& self, GraphDbAccessor& db)
: self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
if (!input_cursor_->Pull(frame, symbol_table)) return false;
// get the origin vertex
TypedValue vertex_value = frame[self_.input_symbol_];
auto v1 = vertex_value.Value<VertexAccessor>();
ExpressionEvaluator evaluator(frame, symbol_table);
// get the destination vertex (possibly an existing node)
VertexAccessor v2 = OtherVertex(frame, symbol_table, evaluator);
// create an edge between the two nodes
switch (self_.edge_atom_->direction_) {
case EdgeAtom::Direction::LEFT:
CreateEdge(v2, v1, evaluator);
break;
case EdgeAtom::Direction::RIGHT:
CreateEdge(v1, v2, evaluator);
break;
case EdgeAtom::Direction::BOTH:
permanent_fail("Undefined direction not allowed in create");
}
return true;
}
private:
CreateExpand& self_;
GraphDbAccessor& db_;
std::unique_ptr<Cursor> input_cursor_;
/**
* Helper function for getting an existing node or creating a new one.
* @return The newly created or already existing node.
*/
VertexAccessor OtherVertex(Frame &frame, SymbolTable &symbol_table,
ExpressionEvaluator &evaluator) {
if (self_.node_existing_) {
TypedValue& dest_node_value =
frame[symbol_table[*self_.node_atom_->identifier_]];
return dest_node_value.Value<VertexAccessor>();
} else {
// the node does not exist, it needs to be created
auto node = db_.insert_vertex();
for (auto label : self_.node_atom_->labels_) node.add_label(label);
for (auto kv : self_.node_atom_->properties_) {
kv.second->Accept(evaluator);
node.PropsSet(kv.first, evaluator.PopBack());
}
frame[symbol_table[*self_.node_atom_->identifier_]] = node;
return node;
}
}
/**
* Helper function for creating an edge.
*
* @param from Origin vertex of the edge.
* @param to Destination vertex of the edge.
* @param evaluator Expression evaluator for property value eval.
*/
void CreateEdge(VertexAccessor& from, VertexAccessor& to,
ExpressionEvaluator& evaluator) {
EdgeAccessor edge =
db_.insert_edge(from, to, self_.edge_atom_->edge_types_[0]);
for (auto kv : self_.edge_atom_->properties_) {
kv.second->Accept(evaluator);
edge.PropsSet(kv.first, evaluator.PopBack());
}
};
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<CreateExpandCursor>(*this, db);
}
private:
// info on what's getting expanded
NodeAtom* node_atom_;
EdgeAtom* edge_atom_;
// the input op and the symbol under which the op's result
// can be found in the frame
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
// if the given node atom refers to an existing node
// (either matched or created)
bool node_existing_;
};
class ScanAll : public LogicalOperator {
public:
ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {}
DEFVISITABLE(LogicalOperatorVisitor);
private:
class ScanAllCursor : public Cursor {
public:
ScanAllCursor(ScanAll& self, GraphDbAccessor& db)
: self_(self),
vertices_(db.vertices()),
vertices_it_(vertices_.begin()) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
if (vertices_it_ == vertices_.end()) return false;
frame[symbol_table[*self_.node_atom_->identifier_]] = *vertices_it_++;
return true;
}
private:
ScanAll& self_;
decltype(std::declval<GraphDbAccessor>().vertices()) vertices_;
decltype(vertices_.begin()) vertices_it_;
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<ScanAllCursor>(*this, db);
}
private:
NodeAtom* node_atom_ = nullptr;
};
/**
* Expansion operator. For a node existing in the frame it
* expands one edge and one node and places them on the frame.
*
* This class does not handle node/edge filtering based on
* properties, labels and edge types. However, it does handle
* cycle filtering.
*
* Cycle filtering means that for a pattern that references
* the same node or edge in two places (for example (n)-->(n)),
* only expansions that match defined equalities are succesfully
* pulled.
*/
class Expand : public LogicalOperator {
using InEdgeT = decltype(std::declval<VertexAccessor>().in());
using InEdgeIteratorT = decltype(std::declval<VertexAccessor>().in().begin());
using OutEdgeT = decltype(std::declval<VertexAccessor>().out());
using OutEdgeIteratorT =
decltype(std::declval<VertexAccessor>().out().begin());
public:
/**
* Creates an expansion.
*
* Cycle-checking is controlled via booleans. A true value
* simply denotes that this expansion references an already
* Pulled node/edge, and should only be checked for equalities
* during expansion.
*
* @param node_atom Describes the node to be expanded. Only the
* identifier is used, labels and properties are ignored.
* @param edge_atom Describes the edge to be expanded. Identifier
* and direction are used, edge type and properties are ignored.
* @param input LogicalOperation that preceeds this one.
* @param input_symbol Symbol that points to a VertexAccessor
* in the Frame that expansion should emanate from.
* @param node_cycle If or not the node to be expanded is already
* present in the Frame and should just be checked for equality.
* @param edge_cycle Same like 'node_cycle', but for edges.
*/
Expand(NodeAtom* node_atom, EdgeAtom* edge_atom,
const std::shared_ptr<LogicalOperator>& input,
const Symbol& input_symbol,
bool node_cycle, bool edge_cycle)
: node_atom_(node_atom),
edge_atom_(edge_atom),
input_(input),
input_symbol_(input_symbol),
node_cycle_(node_cycle),
edge_cycle_(edge_cycle) {}
void Accept(LogicalOperatorVisitor &visitor) override {
visitor.Visit(*this);
input_->Accept(visitor);
visitor.PostVisit(*this);
}
private:
class ExpandCursor : public Cursor {
public:
ExpandCursor(Expand& self, GraphDbAccessor& db)
: self_(self), input_cursor_(self.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (true) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
EdgeAccessor edge = *(*in_edges_it_)++;
if (HandleEdgeCycle(edge, frame, symbol_table) &&
PullNode(edge, EdgeAtom::Direction::LEFT, frame, symbol_table))
return true;
else
continue;
}
// attempt to get a value from the outgoing edges
if (out_edges_ && *out_edges_it_ != out_edges_->end()) {
EdgeAccessor edge = *(*out_edges_it_)++;
if (HandleEdgeCycle(edge, frame, symbol_table) &&
PullNode(edge, EdgeAtom::Direction::RIGHT, frame, symbol_table))
return true;
else
continue;
}
// if we are here, either the edges have not been initialized,
// or they have been exhausted. attempt to initialize the edges,
// if the input is exhausted
if (!InitEdges(frame, symbol_table)) return false;
// we have re-initialized the edges, continue with the loop
}
}
private:
Expand& self_;
std::unique_ptr<Cursor> input_cursor_;
// the iterable over edges and the current edge iterator are referenced via
// unique pointers because they can not be initialized in the constructor of
// this class. they are initialized once for each pull from the input
std::unique_ptr<InEdgeT> in_edges_;
std::unique_ptr<InEdgeIteratorT> in_edges_it_;
std::unique_ptr<OutEdgeT> out_edges_;
std::unique_ptr<OutEdgeIteratorT> out_edges_it_;
bool InitEdges(Frame& frame, SymbolTable& symbol_table) {
if (!input_cursor_->Pull(frame, symbol_table)) return false;
TypedValue vertex_value = frame[self_.input_symbol_];
auto vertex = vertex_value.Value<VertexAccessor>();
auto direction = self_.edge_atom_->direction_;
if (direction == EdgeAtom::Direction::LEFT ||
direction == EdgeAtom::Direction::BOTH) {
in_edges_ = std::make_unique<InEdgeT>(vertex.in());
in_edges_it_ = std::make_unique<InEdgeIteratorT>(in_edges_->begin());
}
if (direction == EdgeAtom::Direction::RIGHT ||
direction == EdgeAtom::Direction::BOTH) {
out_edges_ = std::make_unique<InEdgeT>(vertex.out());
out_edges_it_ = std::make_unique<InEdgeIteratorT>(out_edges_->begin());
}
// TODO add support for Front and Back expansion (when QueryPlanner
// will need it). For now only Back expansion (left to right) is
// supported
// TODO add support for named paths
// TODO add support for uniqueness (edge, vertex)
return true;
}
/**
* For a newly expanded edge handles cycle checking and frame insertion.
*
* @return If or not the given new_edge is a valid expansion. It is not
* valid only when doing an edge-cycle and the new_edge does not match the
* old.
*/
bool HandleEdgeCycle(EdgeAccessor& new_edge, Frame& frame,
SymbolTable& symbol_table) {
if (self_.edge_cycle_) {
TypedValue& old_edge_value =
frame[symbol_table[*self_.edge_atom_->identifier_]];
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
// not doing a cycle, so put the new_edge into the frame and return true
frame[symbol_table[*self_.edge_atom_->identifier_]] = new_edge;
return true;
}
}
/**
* Expands a node for the given newly expanded edge.
*
* @return True if after this call a new node has been successfully
* expanded. Returns false only when doing a node-cycle and the
* new node does not qualify.
*/
bool PullNode(EdgeAccessor& new_edge, EdgeAtom::Direction direction,
Frame& frame, SymbolTable& symbol_table) {
switch (direction) {
case EdgeAtom::Direction::LEFT:
return HandleNodeCycle(new_edge.from(), frame, symbol_table);
case EdgeAtom::Direction::RIGHT:
return HandleNodeCycle(new_edge.to(), frame, symbol_table);
case EdgeAtom::Direction::BOTH:
permanent_fail("Must indicate exact expansion direction here");
}
}
/**
* For a newly expanded node handles cycle checking and frame insertion.
*
* @return If or not the given new_node is a valid expansion. It is not
* valid only when doing a node-cycle and the new_node does not match the
* old.
*/
bool HandleNodeCycle(VertexAccessor new_node, Frame& frame,
SymbolTable& symbol_table) {
if (self_.node_cycle_) {
TypedValue& old_node_value =
frame[symbol_table[*self_.node_atom_->identifier_]];
return old_node_value.Value<VertexAccessor>() == new_node;
} else {
// not doing a cycle, so put the new_edge into the frame and return true
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
return true;
}
}
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<ExpandCursor>(*this, db);
}
private:
// info on what's getting expanded
NodeAtom* node_atom_;
EdgeAtom* edge_atom_;
// the input op and the symbol under which the op's result
// can be found in the frame
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
// if the given node and edge atom refer to symbols
// (query identifiers) that have already been expanded
// and should be just validated in the frame
bool node_cycle_;
bool edge_cycle_;
};
class NodeFilter : public LogicalOperator {
public:
NodeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
NodeAtom* node_atom)
: input_(input), input_symbol_(input_symbol), node_atom_(node_atom) {}
void Accept(LogicalOperatorVisitor &visitor) override {
visitor.Visit(*this);
input_->Accept(visitor);
visitor.PostVisit(*this);
}
private:
class NodeFilterCursor : public Cursor {
public:
NodeFilterCursor(NodeFilter& self, GraphDbAccessor& db)
: self_(self), input_cursor_(self_.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (input_cursor_->Pull(frame, symbol_table)) {
const auto& vertex = frame[self_.input_symbol_].Value<VertexAccessor>();
if (VertexPasses(vertex, frame, symbol_table)) return true;
}
return false;
}
private:
NodeFilter& self_;
std::unique_ptr<Cursor> input_cursor_;
bool VertexPasses(const VertexAccessor& vertex, Frame& frame,
SymbolTable& symbol_table) {
for (auto label : self_.node_atom_->labels_)
if (!vertex.has_label(label)) return false;
ExpressionEvaluator expression_evaluator(frame, symbol_table);
for (auto prop_pair : self_.node_atom_->properties_) {
prop_pair.second->Accept(expression_evaluator);
TypedValue comparison_result =
vertex.PropsAt(prop_pair.first) == expression_evaluator.PopBack();
if (comparison_result.type() == TypedValue::Type::Null ||
!comparison_result.Value<bool>())
return false;
}
return true;
}
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<NodeFilterCursor>(*this, db);
}
private:
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
NodeAtom* node_atom_;
};
class EdgeFilter : public LogicalOperator {
public:
EdgeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
EdgeAtom* edge_atom)
: input_(input), input_symbol_(input_symbol), edge_atom_(edge_atom) {}
void Accept(LogicalOperatorVisitor &visitor) override {
visitor.Visit(*this);
input_->Accept(visitor);
visitor.PostVisit(*this);
}
private:
class EdgeFilterCursor : public Cursor {
public:
EdgeFilterCursor(EdgeFilter& self, GraphDbAccessor& db)
: self_(self), input_cursor_(self_.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (input_cursor_->Pull(frame, symbol_table)) {
const auto& edge = frame[self_.input_symbol_].Value<EdgeAccessor>();
if (EdgePasses(edge, frame, symbol_table)) return true;
}
return false;
}
private:
EdgeFilter& self_;
std::unique_ptr<Cursor> input_cursor_;
bool EdgePasses(const EdgeAccessor& edge, Frame& frame,
SymbolTable& symbol_table) {
for (auto edge_type : self_.edge_atom_->edge_types_)
if (edge.edge_type() != edge_type) return false;
ExpressionEvaluator expression_evaluator(frame, symbol_table);
for (auto prop_pair : self_.edge_atom_->properties_) {
prop_pair.second->Accept(expression_evaluator);
TypedValue comparison_result =
edge.PropsAt(prop_pair.first) == expression_evaluator.PopBack();
if (comparison_result.type() == TypedValue::Type::Null ||
!comparison_result.Value<bool>())
return false;
}
return true;
}
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<EdgeFilterCursor>(*this, db);
}
private:
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
EdgeAtom* edge_atom_;
};
class Produce : public LogicalOperator {
public:
Produce(std::shared_ptr<LogicalOperator> input,
std::vector<NamedExpression*> named_expressions)
: input_(input), named_expressions_(named_expressions) {
children_.emplace_back(input);
}
void Accept(LogicalOperatorVisitor &visitor) override {
visitor.Visit(*this);
input_->Accept(visitor);
visitor.PostVisit(*this);
}
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<ProduceCursor>(*this, db);
}
const auto& named_expressions() { return named_expressions_; }
private:
class ProduceCursor : public Cursor {
public:
ProduceCursor(Produce& self, GraphDbAccessor& db)
: self_(self), self_cursor_(self_.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
ExpressionEvaluator evaluator(frame, symbol_table);
if (self_cursor_->Pull(frame, symbol_table)) {
for (auto named_expr : self_.named_expressions_) {
named_expr->Accept(evaluator);
}
return true;
}
return false;
}
private:
Produce& self_;
std::unique_ptr<Cursor> self_cursor_;
};
private:
std::shared_ptr<LogicalOperator> input_;
std::vector<NamedExpression*> named_expressions_;
};
}