diff --git a/CMakeLists.txt b/CMakeLists.txt index a95637a6d..8f84ebc54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,6 +362,7 @@ set(memgraph_src_files ${src_dir}/query/console.cpp ${src_dir}/query/frontend/ast/cypher_main_visitor.cpp ${src_dir}/query/backend/cpp/typed_value.cpp + ${src_dir}/query/frontend/logical/operator.cpp ${src_dir}/query/frontend/logical/planner.cpp ${src_dir}/query/frontend/semantic/symbol_generator.cpp ) diff --git a/src/query/frontend/logical/operator.cpp b/src/query/frontend/logical/operator.cpp new file mode 100644 index 000000000..35a32b5af --- /dev/null +++ b/src/query/frontend/logical/operator.cpp @@ -0,0 +1,713 @@ +#include "operator.hpp" +#include "query/exceptions.hpp" + + +namespace query { +namespace plan { + +CreateNode::CreateNode(NodeAtom *node_atom, + std::shared_ptr input) + : node_atom_(node_atom), input_(input) {} + +void CreateNode::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + if (input_) input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr CreateNode::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +CreateNode::CreateNodeCursor::CreateNodeCursor(CreateNode &self, + GraphDbAccessor &db) + : self_(self), + db_(db), + input_cursor_(self.input_ ? self.input_->MakeCursor(db) : nullptr) {} + +bool CreateNode::CreateNodeCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + if (input_cursor_) { + if (input_cursor_->Pull(frame, symbol_table)) { + Create(frame, symbol_table); + return true; + } else + return false; + } else if (!did_create_) { + Create(frame, symbol_table); + did_create_ = true; + return true; + } else + return false; +} + +void CreateNode::CreateNodeCursor::Create(Frame &frame, + SymbolTable &symbol_table) { + 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; +} + +CreateExpand::CreateExpand(NodeAtom *node_atom, EdgeAtom *edge_atom, + const std::shared_ptr &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) {} + +void CreateExpand::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr CreateExpand::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +CreateExpand::CreateExpandCursor::CreateExpandCursor(CreateExpand &self, + GraphDbAccessor &db) + : self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {} + +bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + 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(); + + 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, frame, symbol_table, evaluator); + break; + case EdgeAtom::Direction::RIGHT: + CreateEdge(v1, v2, frame, symbol_table, evaluator); + break; + case EdgeAtom::Direction::BOTH: + permanent_fail("Undefined direction not allowed in create"); + } + + return true; +} + +VertexAccessor CreateExpand::CreateExpandCursor::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(); + } 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; + } +} + +void CreateExpand::CreateExpandCursor::CreateEdge( + VertexAccessor &from, VertexAccessor &to, Frame &frame, + SymbolTable &symbol_table, 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()); + } + frame[symbol_table[*self_.edge_atom_->identifier_]] = edge; +} + +ScanAll::ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {} + +std::unique_ptr ScanAll::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +ScanAll::ScanAllCursor::ScanAllCursor(ScanAll &self, GraphDbAccessor &db) + : self_(self), vertices_(db.vertices()), vertices_it_(vertices_.begin()) {} + +bool ScanAll::ScanAllCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + if (vertices_it_ == vertices_.end()) return false; + frame[symbol_table[*self_.node_atom_->identifier_]] = *vertices_it_++; + return true; +} + +Expand::Expand(NodeAtom *node_atom, EdgeAtom *edge_atom, + const std::shared_ptr &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 Expand::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr Expand::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +Expand::ExpandCursor::ExpandCursor(Expand &self, GraphDbAccessor &db) + : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} + +bool Expand::ExpandCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + 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 + } +} + +bool Expand::ExpandCursor::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(); + + auto direction = self_.edge_atom_->direction_; + if (direction == EdgeAtom::Direction::LEFT || + direction == EdgeAtom::Direction::BOTH) { + in_edges_ = std::make_unique(vertex.in()); + in_edges_it_ = std::make_unique(in_edges_->begin()); + } + + if (direction == EdgeAtom::Direction::RIGHT || + direction == EdgeAtom::Direction::BOTH) { + out_edges_ = std::make_unique(vertex.out()); + out_edges_it_ = std::make_unique(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; +} + +bool Expand::ExpandCursor::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() == 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; + } +} + +bool Expand::ExpandCursor::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"); + } +} + +bool Expand::ExpandCursor::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() == 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; + } +} + +NodeFilter::NodeFilter(std::shared_ptr input, + Symbol input_symbol, NodeAtom *node_atom) + : input_(input), input_symbol_(input_symbol), node_atom_(node_atom) {} + +void NodeFilter::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr NodeFilter::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +NodeFilter::NodeFilterCursor::NodeFilterCursor(NodeFilter &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self_.input_->MakeCursor(db)) {} + +bool NodeFilter::NodeFilterCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + while (input_cursor_->Pull(frame, symbol_table)) { + const auto &vertex = frame[self_.input_symbol_].Value(); + if (VertexPasses(vertex, frame, symbol_table)) return true; + } + return false; +} + +bool NodeFilter::NodeFilterCursor::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()) + return false; + } + return true; +} + +EdgeFilter::EdgeFilter(std::shared_ptr input, + Symbol input_symbol, EdgeAtom *edge_atom) + : input_(input), input_symbol_(input_symbol), edge_atom_(edge_atom) {} + +void EdgeFilter::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr EdgeFilter::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} +EdgeFilter::EdgeFilterCursor::EdgeFilterCursor(EdgeFilter &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self_.input_->MakeCursor(db)) {} + +bool EdgeFilter::EdgeFilterCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + while (input_cursor_->Pull(frame, symbol_table)) { + const auto &edge = frame[self_.input_symbol_].Value(); + if (EdgePasses(edge, frame, symbol_table)) return true; + } + return false; +} + +bool EdgeFilter::EdgeFilterCursor::EdgePasses(const EdgeAccessor &edge, + Frame &frame, + SymbolTable &symbol_table) { + // edge type filtering - logical OR + const auto &types = self_.edge_atom_->edge_types_; + GraphDbTypes::EdgeType type = edge.edge_type(); + if (!std::any_of(types.begin(), types.end(), + [type](auto t) { return t == 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()) + return false; + } + return true; +} + +Filter::Filter(const std::shared_ptr &input_, + Expression *expression_) + : input_(input_), expression_(expression_) {} + +void Filter::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr Filter::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +Filter::FilterCursor::FilterCursor(Filter &self, GraphDbAccessor &db) + : self_(self), input_cursor_(self_.input_->MakeCursor(db)) {} + +bool Filter::FilterCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + ExpressionEvaluator evaluator(frame, symbol_table); + while (input_cursor_->Pull(frame, symbol_table)) { + self_.expression_->Accept(evaluator); + TypedValue result = evaluator.PopBack(); + if (result.type() == TypedValue::Type::Null || !result.Value()) + continue; + return true; + } + return false; +} + +Produce::Produce(std::shared_ptr input, + std::vector named_expressions) + : input_(input), named_expressions_(named_expressions) {} + +void Produce::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + if (input_) input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr Produce::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +const std::vector &Produce::named_expressions() { + return named_expressions_; +} + +Produce::ProduceCursor::ProduceCursor(Produce &self, GraphDbAccessor &db) + : self_(self), + input_cursor_(self.input_ ? self_.input_->MakeCursor(db) : nullptr) {} +bool Produce::ProduceCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + ExpressionEvaluator evaluator(frame, symbol_table); + if (input_cursor_) { + if (input_cursor_->Pull(frame, symbol_table)) { + for (auto named_expr : self_.named_expressions_) + named_expr->Accept(evaluator); + return true; + } + return false; + } else if (!did_produce_) { + for (auto named_expr : self_.named_expressions_) + named_expr->Accept(evaluator); + did_produce_ = true; + return true; + } else + return false; +} + +Delete::Delete(const std::shared_ptr &input_, + const std::vector &expressions, bool detach_) + : input_(input_), expressions_(expressions), detach_(detach_) {} + +void Delete::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr Delete::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +Delete::DeleteCursor::DeleteCursor(Delete &self, GraphDbAccessor &db) + : self_(self), db_(db), input_cursor_(self_.input_->MakeCursor(db)) {} + +bool Delete::DeleteCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + ExpressionEvaluator evaluator(frame, symbol_table); + for (Expression *expression : self_.expressions_) { + expression->Accept(evaluator); + TypedValue value = evaluator.PopBack(); + switch (value.type()) { + case TypedValue::Type::Null: + // if we got a Null, that's OK, probably it's an OPTIONAL MATCH + return true; + case TypedValue::Type::Vertex: + if (self_.detach_) + db_.detach_remove_vertex(value.Value()); + else if (!db_.remove_vertex(value.Value())) + throw query::QueryRuntimeException( + "Failed to remove vertex because of it's existing " + "connections. Consider using DETACH DELETE."); + break; + case TypedValue::Type::Edge: + db_.remove_edge(value.Value()); + break; + case TypedValue::Type::Path: + // TODO consider path deletion + default: + throw TypedValueException("Can only delete edges and vertices"); + } + } + return true; +} + +SetProperty::SetProperty(const std::shared_ptr input, + PropertyLookup *lhs, Expression *rhs) + : input_(input), lhs_(lhs), rhs_(rhs) {} + +void SetProperty::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr SetProperty::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +SetProperty::SetPropertyCursor::SetPropertyCursor(SetProperty &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} + +bool SetProperty::SetPropertyCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + ExpressionEvaluator evaluator(frame, symbol_table); + self_.lhs_->expression_->Accept(evaluator); + TypedValue lhs = evaluator.PopBack(); + self_.rhs_->Accept(evaluator); + TypedValue rhs = evaluator.PopBack(); + + // TODO the following code uses implicit TypedValue to PropertyValue + // conversion which throws a TypedValueException if impossible + // this is correct, but the end user will get a not-very-informative + // error message, so address this when improving error feedback + + switch (lhs.type()) { + case TypedValue::Type::Vertex: + lhs.Value().PropsSet(self_.lhs_->property_, rhs); + break; + case TypedValue::Type::Edge: + lhs.Value().PropsSet(self_.lhs_->property_, rhs); + break; + default: + throw QueryRuntimeException( + "Properties can only be set on Vertices and Edges"); + } + return true; +} + +SetProperties::SetProperties(const std::shared_ptr input, + const Symbol input_symbol, Expression *rhs, Op op) + : input_(input), input_symbol_(input_symbol), rhs_(rhs), op_(op) {} + +void SetProperties::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr SetProperties::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +SetProperties::SetPropertiesCursor::SetPropertiesCursor(SetProperties &self, + GraphDbAccessor &db) + : self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {} + +bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + TypedValue lhs = frame[self_.input_symbol_]; + + ExpressionEvaluator evaluator(frame, symbol_table); + self_.rhs_->Accept(evaluator); + TypedValue rhs = evaluator.PopBack(); + + switch (lhs.type()) { + case TypedValue::Type::Vertex: + Set(lhs.Value(), rhs); + break; + case TypedValue::Type::Edge: + Set(lhs.Value(), rhs); + break; + default: + throw QueryRuntimeException( + "Properties can only be set on Vertices and Edges"); + } + return true; +} + +template +void SetProperties::SetPropertiesCursor::Set(TRecordAccessor &record, + const TypedValue &rhs) { + if (self_.op_ == Op::REPLACE) record.PropsClear(); + + auto set_props = [&record](const auto &properties) { + for (const auto &kv : properties) record.PropsSet(kv.first, kv.second); + }; + + switch (rhs.type()) { + case TypedValue::Type::Edge: + set_props(rhs.Value().Properties()); + break; + case TypedValue::Type::Vertex: + set_props(rhs.Value().Properties()); + break; + case TypedValue::Type::Map: { + // TODO the following code uses implicit TypedValue to PropertyValue + // conversion which throws a TypedValueException if impossible + // this is correct, but the end user will get a not-very-informative + // error message, so address this when improving error feedback + for (const auto &kv : rhs.Value>()) + record.PropsSet(db_.property(kv.first), kv.second); + break; + } + default: + throw QueryRuntimeException( + "Can only set Vertices, Edges and maps as properties"); + } +} + +// instantiate the SetProperties function with concrete TRecordAccessor types +template void SetProperties::SetPropertiesCursor::Set( + RecordAccessor &record, const TypedValue &rhs); +template void SetProperties::SetPropertiesCursor::Set( + RecordAccessor &record, const TypedValue &rhs); + +SetLabels::SetLabels(const std::shared_ptr input, + const Symbol input_symbol, + const std::vector &labels) + : input_(input), input_symbol_(input_symbol), labels_(labels) {} + +void SetLabels::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr SetLabels::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +SetLabels::SetLabelsCursor::SetLabelsCursor(SetLabels &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} + +bool SetLabels::SetLabelsCursor::Pull(Frame &frame, SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + TypedValue vertex_value = frame[self_.input_symbol_]; + VertexAccessor vertex = vertex_value.Value(); + for (auto label : self_.labels_) vertex.add_label(label); + + return true; +} + +RemoveProperty::RemoveProperty(const std::shared_ptr input, + PropertyLookup *lhs) + : input_(input), lhs_(lhs) {} + +void RemoveProperty::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr RemoveProperty::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +RemoveProperty::RemovePropertyCursor::RemovePropertyCursor(RemoveProperty &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} + +bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, + SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + ExpressionEvaluator evaluator(frame, symbol_table); + self_.lhs_->expression_->Accept(evaluator); + TypedValue lhs = evaluator.PopBack(); + + switch (lhs.type()) { + case TypedValue::Type::Vertex: + lhs.Value().PropsErase(self_.lhs_->property_); + break; + case TypedValue::Type::Edge: + lhs.Value().PropsErase(self_.lhs_->property_); + break; + default: + // TODO consider throwing a TypedValueException here + // deal with this when we'll be overhauling error-feedback + throw QueryRuntimeException( + "Properties can only be removed on Vertices and Edges"); + } + return true; +} + +RemoveLabels::RemoveLabels(const std::shared_ptr input, + const Symbol input_symbol, + const std::vector &labels) + : input_(input), input_symbol_(input_symbol), labels_(labels) {} + +void RemoveLabels::Accept(LogicalOperatorVisitor &visitor) { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); +} + +std::unique_ptr RemoveLabels::MakeCursor(GraphDbAccessor &db) { + return std::make_unique(*this, db); +} + +RemoveLabels::RemoveLabelsCursor::RemoveLabelsCursor(RemoveLabels &self, + GraphDbAccessor &db) + : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} + +bool RemoveLabels::RemoveLabelsCursor::Pull( + Frame &frame, SymbolTable &symbol_table) { + if (!input_cursor_->Pull(frame, symbol_table)) return false; + + TypedValue vertex_value = frame[self_.input_symbol_]; + VertexAccessor vertex = vertex_value.Value(); + for (auto label : self_.labels_) vertex.remove_label(label); + + return true; +} + +} // namespace plan +} // namespace query diff --git a/src/query/frontend/logical/operator.hpp b/src/query/frontend/logical/operator.hpp index c1a4ac820..5416708ec 100644 --- a/src/query/frontend/logical/operator.hpp +++ b/src/query/frontend/logical/operator.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include -#include #include #include "database/graph_db_accessor.hpp" @@ -44,12 +42,8 @@ using LogicalOperatorVisitor = class LogicalOperator : public ::utils::Visitable { public: - auto children() { return children_; }; virtual std::unique_ptr MakeCursor(GraphDbAccessor &db) = 0; virtual ~LogicalOperator() {} - - protected: - std::vector> children_; }; /** @@ -69,37 +63,18 @@ class CreateNode : public LogicalOperator { * If a valid input, then a node will be created for each * successful pull from the given input. */ - CreateNode(NodeAtom *node_atom, std::shared_ptr input) - : node_atom_(node_atom), input_(input) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - if (input_) input_->Accept(visitor); - visitor.PostVisit(*this); - } + CreateNode(NodeAtom *node_atom, std::shared_ptr input); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: + NodeAtom *node_atom_ = nullptr; + std::shared_ptr input_; + class CreateNodeCursor : public Cursor { public: - CreateNodeCursor(CreateNode &self, GraphDbAccessor &db) - : self_(self), - db_(db), - input_cursor_(self.input_ ? self.input_->MakeCursor(db) : nullptr) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - if (input_cursor_) { - if (input_cursor_->Pull(frame, symbol_table)) { - Create(frame, symbol_table); - return true; - } else - return false; - } else if (!did_create_) { - Create(frame, symbol_table); - did_create_ = true; - return true; - } else - return false; - } + CreateNodeCursor(CreateNode &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; private: CreateNode &self_; @@ -113,131 +88,17 @@ class CreateNode : public LogicalOperator { /** * Creates a single node and places it in the frame. */ - void Create(Frame &frame, SymbolTable &symbol_table) { - 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; - } + void Create(Frame &frame, SymbolTable &symbol_table); }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } - - private: - NodeAtom *node_atom_ = nullptr; - std::shared_ptr input_; }; class CreateExpand : public LogicalOperator { public: CreateExpand(NodeAtom *node_atom, EdgeAtom *edge_atom, const std::shared_ptr &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) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - 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(); - - 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, frame, symbol_table, evaluator); - break; - case EdgeAtom::Direction::RIGHT: - CreateEdge(v1, v2, frame, symbol_table, evaluator); - break; - case EdgeAtom::Direction::BOTH: - permanent_fail("Undefined direction not allowed in create"); - } - - return true; - } - - private: - CreateExpand &self_; - GraphDbAccessor &db_; - std::unique_ptr 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(); - } 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 and adding it - * to the frame. - * - * @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, Frame &frame, - SymbolTable &symbol_table, 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()); - } - frame[symbol_table[*self_.edge_atom_->identifier_]] = edge; - }; - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const Symbol &input_symbol, bool node_existing); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: // info on what's getting expanded @@ -252,40 +113,56 @@ class CreateExpand : public LogicalOperator { // if the given node atom refers to an existing node // (either matched or created) bool node_existing_; + + class CreateExpandCursor : public Cursor { + public: + CreateExpandCursor(CreateExpand &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + CreateExpand &self_; + GraphDbAccessor &db_; + std::unique_ptr 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); + + /** + * Helper function for creating an edge and adding it + * to the frame. + * + * @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, Frame &frame, + SymbolTable &symbol_table, ExpressionEvaluator &evaluator); + }; }; class ScanAll : public LogicalOperator { public: - ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {} + ScanAll(NodeAtom *node_atom); DEFVISITABLE(LogicalOperatorVisitor); + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: + NodeAtom *node_atom_ = nullptr; + 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; - } + ScanAllCursor(ScanAll &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; private: ScanAll &self_; decltype(std::declval().vertices()) vertices_; decltype(vertices_.begin()) vertices_it_; }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } - - private: - NodeAtom *node_atom_ = nullptr; }; /** @@ -330,161 +207,9 @@ class Expand : public LogicalOperator { */ Expand(NodeAtom *node_atom, EdgeAtom *edge_atom, const std::shared_ptr &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 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 in_edges_; - std::unique_ptr in_edges_it_; - std::unique_ptr out_edges_; - std::unique_ptr 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(); - - auto direction = self_.edge_atom_->direction_; - if (direction == EdgeAtom::Direction::LEFT || - direction == EdgeAtom::Direction::BOTH) { - in_edges_ = std::make_unique(vertex.in()); - in_edges_it_ = std::make_unique(in_edges_->begin()); - } - - if (direction == EdgeAtom::Direction::RIGHT || - direction == EdgeAtom::Direction::BOTH) { - out_edges_ = std::make_unique(vertex.out()); - out_edges_it_ = std::make_unique(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() == 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() == 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 MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const Symbol &input_symbol, bool node_cycle, bool edge_cycle); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: // info on what's getting expanded @@ -501,128 +226,112 @@ class Expand : public LogicalOperator { // and should be just validated in the frame bool node_cycle_; bool edge_cycle_; + + class ExpandCursor : public Cursor { + public: + ExpandCursor(Expand &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + Expand &self_; + std::unique_ptr 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 in_edges_; + std::unique_ptr in_edges_it_; + std::unique_ptr out_edges_; + std::unique_ptr out_edges_it_; + + bool InitEdges(Frame &frame, SymbolTable &symbol_table); + + /** + * 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); + + /** + * 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); + + /** + * 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); + }; }; class NodeFilter : public LogicalOperator { public: NodeFilter(std::shared_ptr 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(); - if (VertexPasses(vertex, frame, symbol_table)) return true; - } - return false; - } - - private: - NodeFilter &self_; - std::unique_ptr 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()) - return false; - } - return true; - } - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + NodeAtom *node_atom); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; const Symbol input_symbol_; NodeAtom *node_atom_; + + class NodeFilterCursor : public Cursor { + public: + NodeFilterCursor(NodeFilter &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + NodeFilter &self_; + std::unique_ptr input_cursor_; + + /** Helper function for checking if the given vertex + * passes this filter. */ + bool VertexPasses(const VertexAccessor &vertex, Frame &frame, + SymbolTable &symbol_table); + }; }; class EdgeFilter : public LogicalOperator { public: EdgeFilter(std::shared_ptr 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(); - if (EdgePasses(edge, frame, symbol_table)) return true; - } - return false; - } - - private: - EdgeFilter &self_; - std::unique_ptr input_cursor_; - - bool EdgePasses(const EdgeAccessor &edge, Frame &frame, - SymbolTable &symbol_table) { - // edge type filtering - logical OR - const auto &types = self_.edge_atom_->edge_types_; - GraphDbTypes::EdgeType type = edge.edge_type(); - if (!std::any_of(types.begin(), types.end(), - [type](auto t) { return t == 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()) - return false; - } - return true; - } - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + EdgeAtom *edge_atom); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; const Symbol input_symbol_; EdgeAtom *edge_atom_; + + class EdgeFilterCursor : public Cursor { + public: + EdgeFilterCursor(EdgeFilter &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + EdgeFilter &self_; + std::unique_ptr input_cursor_; + + /** Helper function for checking if the given edge satisfied + * the criteria of this edge filter. */ + bool EdgePasses(const EdgeAccessor &edge, Frame &frame, + SymbolTable &symbol_table); + }; }; /** @@ -633,46 +342,23 @@ class EdgeFilter : public LogicalOperator { class Filter : public LogicalOperator { public: Filter(const std::shared_ptr &input_, - Expression *expression_) - : input_(input_), expression_(expression_) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } + Expression *expression_); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: + std::shared_ptr input_; + Expression *expression_; + class FilterCursor : public Cursor { public: - FilterCursor(Filter &self, GraphDbAccessor &db) - : self_(self), input_cursor_(self_.input_->MakeCursor(db)) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - ExpressionEvaluator evaluator(frame, symbol_table); - while (input_cursor_->Pull(frame, symbol_table)) { - self_.expression_->Accept(evaluator); - TypedValue result = evaluator.PopBack(); - if (result.type() == TypedValue::Type::Null || !result.Value()) - continue; - return true; - } - return false; - } + FilterCursor(Filter &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; private: Filter &self_; std::unique_ptr input_cursor_; }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } - - private: - std::shared_ptr input_; - Expression *expression_; }; /** @@ -689,46 +375,19 @@ class Filter : public LogicalOperator { class Produce : public LogicalOperator { public: Produce(std::shared_ptr input, - std::vector named_expressions) - : input_(input), named_expressions_(named_expressions) { - children_.emplace_back(input); - } - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - if (input_) input_->Accept(visitor); - visitor.PostVisit(*this); - } - - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } - - const auto &named_expressions() { return named_expressions_; } + std::vector named_expressions); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; + const std::vector &named_expressions(); private: + std::shared_ptr input_; + std::vector named_expressions_; + class ProduceCursor : public Cursor { public: - ProduceCursor(Produce &self, GraphDbAccessor &db) - : self_(self), - input_cursor_(self.input_ ? self_.input_->MakeCursor(db) : nullptr) {} - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - ExpressionEvaluator evaluator(frame, symbol_table); - if (input_cursor_) { - if (input_cursor_->Pull(frame, symbol_table)) { - for (auto named_expr : self_.named_expressions_) - named_expr->Accept(evaluator); - return true; - } - return false; - } else if (!did_produce_) { - for (auto named_expr : self_.named_expressions_) - named_expr->Accept(evaluator); - did_produce_ = true; - return true; - } else - return false; - } + ProduceCursor(Produce &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; private: Produce &self_; @@ -737,10 +396,6 @@ class Produce : public LogicalOperator { // control switch when creating only one node (nullptr input) bool did_produce_{false}; }; - - private: - std::shared_ptr input_; - std::vector named_expressions_; }; /** @@ -751,62 +406,9 @@ class Produce : public LogicalOperator { class Delete : public LogicalOperator { public: Delete(const std::shared_ptr &input_, - const std::vector &expressions, bool detach_) - : input_(input_), expressions_(expressions), detach_(detach_) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - private: - class DeleteCursor : public Cursor { - public: - DeleteCursor(Delete &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; - - ExpressionEvaluator evaluator(frame, symbol_table); - for (Expression *expression : self_.expressions_) { - expression->Accept(evaluator); - TypedValue value = evaluator.PopBack(); - switch (value.type()) { - case TypedValue::Type::Null: - // if we got a Null, that's OK, probably it's an OPTIONAL MATCH - return true; - case TypedValue::Type::Vertex: - if (self_.detach_) - db_.detach_remove_vertex(value.Value()); - else if (!db_.remove_vertex(value.Value())) - throw query::QueryRuntimeException( - "Failed to remove vertex because of it's existing " - "connections. Consider using DETACH DELETE."); - break; - case TypedValue::Type::Edge: - db_.remove_edge(value.Value()); - break; - case TypedValue::Type::Path: - // TODO consider path deletion - default: - throw TypedValueException("Can only delete edges and vertices"); - } - } - return true; - } - - private: - Delete &self_; - GraphDbAccessor &db_; - std::unique_ptr input_cursor_; - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const std::vector &expressions, bool detach_); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; @@ -815,6 +417,17 @@ class Delete : public LogicalOperator { // if not detached, and has connections, an error is raised // ignored when deleting edges bool detach_; + + class DeleteCursor : public Cursor { + public: + DeleteCursor(Delete &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + Delete &self_; + GraphDbAccessor &db_; + std::unique_ptr input_cursor_; + }; }; /** @@ -827,63 +440,24 @@ class Delete : public LogicalOperator { class SetProperty : public LogicalOperator { public: SetProperty(const std::shared_ptr input, PropertyLookup *lhs, - Expression *rhs) - : input_(input), lhs_(lhs), rhs_(rhs) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - private: - class SetPropertyCursor : public Cursor { - public: - SetPropertyCursor(SetProperty &self, GraphDbAccessor &db) - : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - if (!input_cursor_->Pull(frame, symbol_table)) return false; - - ExpressionEvaluator evaluator(frame, symbol_table); - self_.lhs_->expression_->Accept(evaluator); - TypedValue lhs = evaluator.PopBack(); - self_.rhs_->Accept(evaluator); - TypedValue rhs = evaluator.PopBack(); - - // TODO the following code uses implicit TypedValue to PropertyValue - // conversion which throws a TypedValueException if impossible - // this is correct, but the end user will get a not-very-informative - // error message, so address this when improving error feedback - - switch (lhs.type()) { - case TypedValue::Type::Vertex: - lhs.Value().PropsSet(self_.lhs_->property_, rhs); - break; - case TypedValue::Type::Edge: - lhs.Value().PropsSet(self_.lhs_->property_, rhs); - break; - default: - throw QueryRuntimeException( - "Properties can only be set on Vertices and Edges"); - } - return true; - } - - private: - SetProperty &self_; - std::unique_ptr input_cursor_; - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + Expression *rhs); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; PropertyLookup *lhs_; Expression *rhs_; + + class SetPropertyCursor : public Cursor { + public: + SetPropertyCursor(SetProperty &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + SetProperty &self_; + std::unique_ptr input_cursor_; + }; }; /** @@ -907,90 +481,34 @@ class SetProperties : public LogicalOperator { enum class Op { UPDATE, REPLACE }; SetProperties(const std::shared_ptr input, - const Symbol input_symbol, Expression *rhs, Op op) - : input_(input), input_symbol_(input_symbol), rhs_(rhs), op_(op) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - private: - class SetPropertiesCursor : public Cursor { - public: - SetPropertiesCursor(SetProperties &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; - - TypedValue lhs = frame[self_.input_symbol_]; - - ExpressionEvaluator evaluator(frame, symbol_table); - self_.rhs_->Accept(evaluator); - TypedValue rhs = evaluator.PopBack(); - - switch (lhs.type()) { - case TypedValue::Type::Vertex: - SetProperties(lhs.Value(), rhs); - break; - case TypedValue::Type::Edge: - SetProperties(lhs.Value(), rhs); - break; - default: - throw QueryRuntimeException( - "Properties can only be set on Vertices and Edges"); - } - return true; - } - - private: - SetProperties &self_; - GraphDbAccessor &db_; - std::unique_ptr input_cursor_; - - template - void SetProperties(TRecordAccessor &record, const TypedValue &rhs) { - if (self_.op_ == Op::REPLACE) record.PropsClear(); - - auto set_props = [&record](const auto &properties) { - for (const auto &kv : properties) record.PropsSet(kv.first, kv.second); - }; - - switch (rhs.type()) { - case TypedValue::Type::Edge: - set_props(rhs.Value().Properties()); - break; - case TypedValue::Type::Vertex: - set_props(rhs.Value().Properties()); - break; - case TypedValue::Type::Map: { - // TODO the following code uses implicit TypedValue to PropertyValue - // conversion which throws a TypedValueException if impossible - // this is correct, but the end user will get a not-very-informative - // error message, so address this when improving error feedback - for (const auto &kv : rhs.Value>()) - record.PropsSet(db_.property(kv.first), kv.second); - break; - } - default: - throw QueryRuntimeException( - "Can only set Vertices, Edges and maps as properties"); - } - } - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const Symbol input_symbol, Expression *rhs, Op op); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; const Symbol input_symbol_; Expression *rhs_; Op op_; + + class SetPropertiesCursor : public Cursor { + public: + SetPropertiesCursor(SetProperties &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + SetProperties &self_; + GraphDbAccessor &db_; + std::unique_ptr input_cursor_; + + /** Helper function that sets the given values on either + * a VertexRecord or an EdgeRecord. + * @tparam TRecordAccessor Either RecordAccessor or + * RecordAccessor + */ + template + void Set(TRecordAccessor &record, const TypedValue &rhs); + }; }; /** @@ -1002,45 +520,24 @@ class SetLabels : public LogicalOperator { public: SetLabels(const std::shared_ptr input, const Symbol input_symbol, - const std::vector &labels) - : input_(input), input_symbol_(input_symbol), labels_(labels) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - private: - class SetLabelsCursor : public Cursor { - public: - SetLabelsCursor(SetLabels &self, GraphDbAccessor &db) - : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - if (!input_cursor_->Pull(frame, symbol_table)) return false; - - TypedValue vertex_value = frame[self_.input_symbol_]; - VertexAccessor vertex = vertex_value.Value(); - for (auto label : self_.labels_) vertex.add_label(label); - - return true; - } - - private: - SetLabels &self_; - std::unique_ptr input_cursor_; - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const std::vector &labels); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; const Symbol input_symbol_; std::vector labels_; + + class SetLabelsCursor : public Cursor { + public: + SetLabelsCursor(SetLabels &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + SetLabels &self_; + std::unique_ptr input_cursor_; + }; }; /** @@ -1050,58 +547,25 @@ class SetLabels : public LogicalOperator { class RemoveProperty : public LogicalOperator { public: RemoveProperty(const std::shared_ptr input, - PropertyLookup *lhs) - : input_(input), lhs_(lhs) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } + PropertyLookup *lhs); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: + std::shared_ptr input_; + PropertyLookup *lhs_; + class RemovePropertyCursor : public Cursor { public: - RemovePropertyCursor(RemoveProperty &self, GraphDbAccessor &db) - : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - if (!input_cursor_->Pull(frame, symbol_table)) return false; - - ExpressionEvaluator evaluator(frame, symbol_table); - self_.lhs_->expression_->Accept(evaluator); - TypedValue lhs = evaluator.PopBack(); - - switch (lhs.type()) { - case TypedValue::Type::Vertex: - lhs.Value().PropsErase(self_.lhs_->property_); - break; - case TypedValue::Type::Edge: - lhs.Value().PropsErase(self_.lhs_->property_); - break; - default: - // TODO consider throwing a TypedValueException here - // deal with this when we'll be overhauling error-feedback - throw QueryRuntimeException( - "Properties can only be removed on Vertices and Edges"); - } - return true; - } + RemovePropertyCursor(RemoveProperty &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; private: RemoveProperty &self_; std::unique_ptr input_cursor_; }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } - - private: - std::shared_ptr input_; - PropertyLookup *lhs_; }; + /** * Logical operator for removing an arbitrary number of * labels on a Vertex. If a label does not exist on a Vertex, @@ -1111,45 +575,24 @@ class RemoveLabels : public LogicalOperator { public: RemoveLabels(const std::shared_ptr input, const Symbol input_symbol, - const std::vector &labels) - : input_(input), input_symbol_(input_symbol), labels_(labels) {} - - void Accept(LogicalOperatorVisitor &visitor) override { - visitor.Visit(*this); - input_->Accept(visitor); - visitor.PostVisit(*this); - } - - private: - class RemoveLabelsCursor : public Cursor { - public: - RemoveLabelsCursor(RemoveLabels &self, GraphDbAccessor &db) - : self_(self), input_cursor_(self.input_->MakeCursor(db)) {} - - bool Pull(Frame &frame, SymbolTable &symbol_table) override { - if (!input_cursor_->Pull(frame, symbol_table)) return false; - - TypedValue vertex_value = frame[self_.input_symbol_]; - VertexAccessor vertex = vertex_value.Value(); - for (auto label : self_.labels_) vertex.remove_label(label); - - return true; - } - - private: - RemoveLabels &self_; - std::unique_ptr input_cursor_; - }; - - public: - std::unique_ptr MakeCursor(GraphDbAccessor &db) override { - return std::make_unique(*this, db); - } + const std::vector &labels); + void Accept(LogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(GraphDbAccessor &db) override; private: std::shared_ptr input_; const Symbol input_symbol_; std::vector labels_; + + class RemoveLabelsCursor : public Cursor { + public: + RemoveLabelsCursor(RemoveLabels &self, GraphDbAccessor &db); + bool Pull(Frame &frame, SymbolTable &symbol_table) override; + + private: + RemoveLabels &self_; + std::unique_ptr input_cursor_; + }; }; } // namespace plan diff --git a/src/utils/visitor/visitable.hpp b/src/utils/visitor/visitable.hpp index 0e91d5439..6c86b1ee3 100644 --- a/src/utils/visitor/visitable.hpp +++ b/src/utils/visitor/visitable.hpp @@ -27,6 +27,7 @@ class Visitable { void Accept(TVisitor &visitor) override \ { visitor.Visit(*this); visitor.PostVisit(*this); } + }; } diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index 513b8951f..020130daf 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -15,6 +15,7 @@ #include "query/context.hpp" #include "query/frontend/interpret/interpret.hpp" #include "query/frontend/logical/planner.hpp" +#include "query/exceptions.hpp" #include "query_common.hpp"