From 1280e77fd3b9287e09875db6053dca810ab02870 Mon Sep 17 00:00:00 2001
From: florijan <florijan@memgraph.io>
Date: Mon, 27 Mar 2017 13:09:14 +0200
Subject: [PATCH] Query - Logical - Delete op added and tested. Minor
 refactors.

Reviewers: buda, teon.banek, mislav.bradac

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D184
---
 src/query/exceptions.hpp                  |   9 +
 src/query/frontend/logical/operator.hpp   |  92 ++++++-
 src/query/frontend/logical/planner.cpp    |  10 +-
 src/query/frontend/logical/planner.hpp    |   7 +-
 src/query/interpreter.hpp                 |  30 +-
 src/storage/util.hpp                      |   7 +-
 tests/unit/graph_db_accessor.cpp          |  75 ++++-
 tests/unit/interpreter.cpp                | 319 ++++++++--------------
 tests/unit/query_common.hpp               |  10 +-
 tests/unit/query_expression_evaluator.cpp | 234 ++++++++++++++++
 tests/unit/query_planner.cpp              |   7 +-
 11 files changed, 538 insertions(+), 262 deletions(-)
 create mode 100644 tests/unit/query_expression_evaluator.cpp

diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp
index 872769ea2..ae6ebda9a 100644
--- a/src/query/exceptions.hpp
+++ b/src/query/exceptions.hpp
@@ -49,6 +49,15 @@ class TypeMismatchError : public SemanticException {
             name, datum, expected)) {}
 };
 
+/**
+ * An exception for an illegal operation that can not be detected
+ * before the query starts executing over data.
+ */
+class QueryRuntimeException : public BasicException {
+public:
+  using BasicException::BasicException;
+};
+
 class CppCodeGeneratorException : public StacktraceException {
  public:
   using StacktraceException::StacktraceException;
diff --git a/src/query/frontend/logical/operator.hpp b/src/query/frontend/logical/operator.hpp
index e9a3e6065..2a53c8099 100644
--- a/src/query/frontend/logical/operator.hpp
+++ b/src/query/frontend/logical/operator.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <memory>
+#include <query/exceptions.hpp>
 #include <sstream>
 #include <vector>
 
@@ -12,6 +13,7 @@
 #include "utils/visitor/visitor.hpp"
 
 namespace query {
+namespace plan {
 
 class Cursor {
  public:
@@ -27,10 +29,11 @@ class NodeFilter;
 class EdgeFilter;
 class Filter;
 class Produce;
+class Delete;
 
 using LogicalOperatorVisitor =
     ::utils::Visitor<CreateNode, CreateExpand, ScanAll, Expand, NodeFilter,
-                     EdgeFilter, Filter, Produce>;
+                     EdgeFilter, Filter, Produce, Delete>;
 
 class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
  public:
@@ -55,7 +58,7 @@ class CreateNode : public LogicalOperator {
    *
    * @param node_atom
    * @param input Optional. If nullptr, then a single node will be
-   *    created (a single successfull Pull from this Op's Cursor).
+   *    created (a single successful Pull from this Op's Cursor).
    *    If a valid input, then a node will be created for each
    *    successful pull from the given input.
    */
@@ -687,10 +690,10 @@ class Produce : public LogicalOperator {
   class ProduceCursor : public Cursor {
    public:
     ProduceCursor(Produce &self, GraphDbAccessor &db)
-        : self_(self), self_cursor_(self_.input_->MakeCursor(db)) {}
+        : self_(self), input_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)) {
+      if (input_cursor_->Pull(frame, symbol_table)) {
         for (auto named_expr : self_.named_expressions_) {
           named_expr->Accept(evaluator);
         }
@@ -701,11 +704,88 @@ class Produce : public LogicalOperator {
 
    private:
     Produce &self_;
-    std::unique_ptr<Cursor> self_cursor_;
+    std::unique_ptr<Cursor> input_cursor_;
   };
 
  private:
   std::shared_ptr<LogicalOperator> input_;
   std::vector<NamedExpression *> named_expressions_;
 };
-}
+
+/**
+ * Operator for deleting vertices and edges.
+ * Has a flag for using DETACH DELETE when deleting
+ * vertices.
+ */
+class Delete : public LogicalOperator {
+ public:
+  Delete(const std::shared_ptr<LogicalOperator> &input_,
+         const std::vector<Expression *> &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<VertexAccessor>());
+            else if (!db_.remove_vertex(value.Value<VertexAccessor>()))
+              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<EdgeAccessor>());
+            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<Cursor> input_cursor_;
+  };
+
+ public:
+  std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override {
+    return std::make_unique<DeleteCursor>(*this, db);
+  }
+
+ private:
+  std::shared_ptr<LogicalOperator> input_;
+  std::vector<Expression *> expressions_;
+  // if the vertex should be detached before deletion
+  // if not detached, and has connections, an error is raised
+  // ignored when deleting edges
+  bool detach_;
+};
+} // namespace plan
+} // namespace query
+
+
diff --git a/src/query/frontend/logical/planner.cpp b/src/query/frontend/logical/planner.cpp
index d127bdfc3..98e87f994 100644
--- a/src/query/frontend/logical/planner.cpp
+++ b/src/query/frontend/logical/planner.cpp
@@ -6,6 +6,7 @@
 #include "utils/exceptions/not_yet_implemented.hpp"
 
 namespace query {
+namespace plan {
 
 namespace {
 
@@ -58,7 +59,7 @@ auto ReducePattern(
 }
 
 auto GenCreateForPattern(Pattern &pattern, LogicalOperator *input_op,
-                         const SymbolTable &symbol_table,
+                         const query::SymbolTable &symbol_table,
                          std::unordered_set<int> bound_symbols) {
   auto base = [&](NodeAtom *node) -> LogicalOperator * {
     if (BindSymbol(bound_symbols, symbol_table.at(*node->identifier_)))
@@ -89,7 +90,7 @@ auto GenCreateForPattern(Pattern &pattern, LogicalOperator *input_op,
 }
 
 auto GenCreate(Create &create, LogicalOperator *input_op,
-               const SymbolTable &symbol_table,
+               const query::SymbolTable &symbol_table,
                std::unordered_set<int> bound_symbols) {
   auto last_op = input_op;
   for (auto pattern : create.patterns_) {
@@ -100,7 +101,7 @@ auto GenCreate(Create &create, LogicalOperator *input_op,
 }
 
 auto GenMatch(Match &match, LogicalOperator *input_op,
-              const SymbolTable &symbol_table,
+              const query::SymbolTable &symbol_table,
               std::unordered_set<int> &bound_symbols) {
   auto base = [&](NodeAtom *node) {
     if (input_op) {
@@ -166,7 +167,7 @@ auto GenReturn(Return &ret, LogicalOperator *input_op) {
 }  // namespace
 
 std::unique_ptr<LogicalOperator> MakeLogicalPlan(
-    Query &query, const SymbolTable &symbol_table) {
+    query::Query &query, const query::SymbolTable &symbol_table) {
   // TODO: Extract functions and state into a class with methods. Possibly a
   // visitor or similar to avoid all those dynamic casts.
   LogicalOperator *input_op = nullptr;
@@ -190,4 +191,5 @@ std::unique_ptr<LogicalOperator> MakeLogicalPlan(
   return std::unique_ptr<LogicalOperator>(input_op);
 }
 
+}  // namespace plan
 }  // namespace query
diff --git a/src/query/frontend/logical/planner.hpp b/src/query/frontend/logical/planner.hpp
index 59c0af551..d784d204a 100644
--- a/src/query/frontend/logical/planner.hpp
+++ b/src/query/frontend/logical/planner.hpp
@@ -9,9 +9,12 @@ namespace query {
 class Query;
 class SymbolTable;
 
+namespace plan {
+
 // Returns the root of LogicalOperator tree. The tree is constructed by
 // traversing the given AST Query node. SymbolTable is used to determine inputs
 // and outputs of certain operators.
-std::unique_ptr<LogicalOperator>
-MakeLogicalPlan(Query &query, const SymbolTable &symbol_table);
+std::unique_ptr<LogicalOperator> MakeLogicalPlan(
+    query::Query &query, const query::SymbolTable &symbol_table);
+}
 }
diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp
index 91c759a6a..f1060aaf4 100644
--- a/src/query/interpreter.hpp
+++ b/src/query/interpreter.hpp
@@ -15,7 +15,6 @@ namespace query {
 template <typename Stream>
 void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
                Stream &stream) {
-
   clock_t start_time = clock();
 
   Config config;
@@ -27,7 +26,7 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
   auto low_level_tree = parser.tree();
 
   // AST -> high level tree
-  query::frontend::CypherMainVisitor visitor(ctx);
+  frontend::CypherMainVisitor visitor(ctx);
   visitor.visit(low_level_tree);
   auto high_level_tree = visitor.query();
 
@@ -37,12 +36,12 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
   high_level_tree->Accept(symbol_generator);
 
   // high level tree -> logical plan
-  auto logical_plan = MakeLogicalPlan(*high_level_tree, symbol_table);
+  auto logical_plan = plan::MakeLogicalPlan(*high_level_tree, symbol_table);
 
   // generate frame based on symbol table max_position
   Frame frame(symbol_table.max_position());
 
-  if (auto produce = dynamic_cast<Produce *>(logical_plan.get())) {
+  if (auto produce = dynamic_cast<plan::Produce *>(logical_plan.get())) {
     // top level node in the operator tree is a produce (return)
     // so stream out results
 
@@ -64,24 +63,23 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
       for (auto &symbol : symbols) values.emplace_back(frame[symbol]);
       stream.Result(values);
     }
-
-    summary["type"] = "r";
-  } else if (auto create = dynamic_cast<CreateNode *>(logical_plan.get())) {
-    auto cursor = create->MakeCursor(db_accessor);
-    while (cursor->Pull(frame, symbol_table)) {
+  } else if (dynamic_cast<plan::CreateNode *>(logical_plan.get()) ||
+             dynamic_cast<plan::CreateExpand *>(logical_plan.get()) ||
+             dynamic_cast<Delete *>(logical_plan.get())) {
+    auto cursor = logical_plan.get()->MakeCursor(db_accessor);
+    while (cursor->Pull(frame, symbol_table))
       continue;
-    }
-  } else if (auto create = dynamic_cast<CreateExpand *>(logical_plan.get())) {
-    auto cursor = create->MakeCursor(db_accessor);
-    while (cursor->Pull(frame, symbol_table)) {
-      continue;
-    }
   }
 
   clock_t end_time = clock();
   double time_second = double(end_time - start_time) / CLOCKS_PER_SEC;
   summary["query_time_sec"] = TypedValue(time_second);
+  // TODO set summary['type'] based on transaction metadata
+  // the type can't be determined based only on top level LogicalOp
+  // (for example MATCH DELETE RETURN will have Produce as it's top)
+  // for now always se "rw" because something must be set, but it doesn't
+  // have to be correct (for Bolt clients)
+  summary["type"] = "rw";
   stream.Summary(summary);
-
 }
 }
diff --git a/src/storage/util.hpp b/src/storage/util.hpp
index bf54db26b..306b7d1c4 100644
--- a/src/storage/util.hpp
+++ b/src/storage/util.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cppitertools/reversed.hpp>
 #include "cppitertools/imap.hpp"
 
 /**
@@ -16,5 +17,9 @@ template <typename TAccessor, typename TIterable>
 auto make_accessor_iterator(const TIterable &records, GraphDbAccessor &db_accessor) {
   return iter::imap([&db_accessor](auto vlist) {
     return TAccessor(*vlist, db_accessor);
-  }, records);
+    // note that here we iterate over records in REVERSED order
+    // this is necessary for DETACH DELETE (see GraphDbAccessor)
+    // which deletes items from relationship collections in a
+    // vertex accessor
+  }, iter::reversed(records));
 }
diff --git a/tests/unit/graph_db_accessor.cpp b/tests/unit/graph_db_accessor.cpp
index 7a1c9ef65..badbdb414 100644
--- a/tests/unit/graph_db_accessor.cpp
+++ b/tests/unit/graph_db_accessor.cpp
@@ -162,52 +162,98 @@ TEST(GraphDbAccessorTest, DetachRemoveVertex) {
   Dbms dbms;
   auto dba = dbms.active();
 
-  // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
+  // setup (v1)- []->(v2)<-[]-(v3)<-[]-(v4)
   auto va1 = dba->insert_vertex();
   auto va2 = dba->insert_vertex();
   auto va3 = dba->insert_vertex();
-  dba->insert_edge(va1, va2, dba->edge_type("likes"));
-  dba->insert_edge(va1, va3, dba->edge_type("likes"));
+  auto va4 = dba->insert_vertex();
+  auto edge_type = dba->edge_type("type");
+  dba->insert_edge(va1, va2, edge_type);
+  dba->insert_edge(va1, va3, edge_type);
+  dba->insert_edge(va4, va3, edge_type);
   dba->advance_command();
 
   // ensure that plain remove does NOT work
-  EXPECT_EQ(CountVertices(*dba), 3);
-  EXPECT_EQ(CountEdges(*dba), 2);
+  EXPECT_EQ(CountVertices(*dba), 4);
+  EXPECT_EQ(CountEdges(*dba), 3);
   EXPECT_FALSE(dba->remove_vertex(va1));
   EXPECT_FALSE(dba->remove_vertex(va2));
   EXPECT_FALSE(dba->remove_vertex(va3));
-  EXPECT_EQ(CountVertices(*dba), 3);
-  EXPECT_EQ(CountEdges(*dba), 2);
+  EXPECT_EQ(CountVertices(*dba), 4);
+  EXPECT_EQ(CountEdges(*dba), 3);
 
-  // make a new transaction because at the moment deletions
-  // in the same transaction are not visible
-  // DETACH REMOVE V3
-  // new situation: (v1) - [:likes] -> (v2)
   dba->detach_remove_vertex(va3);
   dba->advance_command();
 
+  EXPECT_EQ(CountVertices(*dba), 3);
+  EXPECT_EQ(CountEdges(*dba), 1);
+  EXPECT_TRUE(dba->remove_vertex(va4));
+  dba->advance_command();
+
   EXPECT_EQ(CountVertices(*dba), 2);
   EXPECT_EQ(CountEdges(*dba), 1);
   for (auto va : dba->vertices()) EXPECT_FALSE(dba->remove_vertex(va));
-
   dba->advance_command();
+
   EXPECT_EQ(CountVertices(*dba), 2);
   EXPECT_EQ(CountEdges(*dba), 1);
-
   for (auto va : dba->vertices()) {
     EXPECT_FALSE(dba->remove_vertex(va));
     dba->detach_remove_vertex(va);
     break;
   }
-
   dba->advance_command();
+
   EXPECT_EQ(CountVertices(*dba), 1);
   EXPECT_EQ(CountEdges(*dba), 0);
 
   // remove the last vertex, it has no connections
   // so that should work
   for (auto va : dba->vertices()) EXPECT_TRUE(dba->remove_vertex(va));
+  dba->advance_command();
 
+  EXPECT_EQ(CountVertices(*dba), 0);
+  EXPECT_EQ(CountEdges(*dba), 0);
+}
+
+TEST(GraphDbAccessorTest, DetachRemoveVertexMultiple) {
+  // This test checks that we can detach remove the
+  // same vertex / edge multiple times
+
+  Dbms dbms;
+  auto dba = dbms.active();
+
+  // setup: make a fully connected N graph
+  // with cycles too!
+  int N = 7;
+  std::vector<VertexAccessor> vertices;
+  auto edge_type = dba->edge_type("edge");
+  for (int i = 0; i < N; ++i)
+    vertices.emplace_back(dba->insert_vertex());
+  for (int j = 0; j < N; ++j)
+    for (int k = 0; k < N; ++k)
+      dba->insert_edge(vertices[j], vertices[k], edge_type);
+  dba->advance_command();
+
+  EXPECT_EQ(CountVertices(*dba), N);
+  EXPECT_EQ(CountEdges(*dba), N * N);
+
+  // detach delete one edge
+  dba->detach_remove_vertex(vertices[0]);
+  dba->advance_command();
+  EXPECT_EQ(CountVertices(*dba), N - 1);
+  EXPECT_EQ(CountEdges(*dba), (N - 1) * (N - 1));
+
+  // detach delete two neighboring edges
+  dba->detach_remove_vertex(vertices[1]);
+  dba->detach_remove_vertex(vertices[2]);
+  dba->advance_command();
+  EXPECT_EQ(CountVertices(*dba), N - 3);
+  EXPECT_EQ(CountEdges(*dba), (N - 3) * (N - 3));
+
+  // detach delete everything, buwahahahaha
+  for (int l = 3; l < N ; ++l)
+    dba->detach_remove_vertex(vertices[l]);
   dba->advance_command();
   EXPECT_EQ(CountVertices(*dba), 0);
   EXPECT_EQ(CountEdges(*dba), 0);
@@ -257,5 +303,6 @@ TEST(GraphDbAccessorTest, Properties) {
 
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
+//  ::testing::GTEST_FLAG(filter) = "*.DetachRemoveVertex";
   return RUN_ALL_TESTS();
 }
diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp
index c71184ec4..54f4644c6 100644
--- a/tests/unit/interpreter.cpp
+++ b/tests/unit/interpreter.cpp
@@ -15,12 +15,11 @@
 #include "query/context.hpp"
 #include "query/frontend/interpret/interpret.hpp"
 #include "query/frontend/logical/planner.hpp"
-#include "query/frontend/opencypher/parser.hpp"
-#include "query/frontend/semantic/symbol_generator.hpp"
 
 #include "query_common.hpp"
 
 using namespace query;
+using namespace query::plan;
 
 /**
  * Helper function that collects all the results from the given
@@ -63,10 +62,10 @@ auto CollectProduce(std::shared_ptr<Produce> produce, SymbolTable &symbol_table,
   return stream;
 }
 
-void ExecuteCreate(std::shared_ptr<LogicalOperator> create, GraphDbAccessor &db,
-                   SymbolTable symbol_table) {
+void PullAll(std::shared_ptr<LogicalOperator> logical_op, GraphDbAccessor &db,
+             SymbolTable symbol_table) {
   Frame frame(symbol_table.max_position());
-  auto cursor = create->MakeCursor(db);
+  auto cursor = logical_op->MakeCursor(db);
   while (cursor->Pull(frame, symbol_table)) {
     continue;
   }
@@ -256,7 +255,7 @@ TEST(Interpreter, CreateNodeWithAttributes) {
   node->properties_[property] = LITERAL(42);
 
   auto create = std::make_shared<CreateNode>(node, nullptr);
-  ExecuteCreate(create, *dba, symbol_table);
+  PullAll(create, *dba, symbol_table);
   dba->advance_command();
 
   // count the number of vertices
@@ -356,7 +355,7 @@ TEST(Interpreter, CreateExpand) {
     auto create_op = std::make_shared<CreateNode>(n, nullptr);
     auto create_expand =
         std::make_shared<CreateExpand>(m, r, create_op, n_sym, cycle);
-    ExecuteCreate(create_expand, *dba, symbol_table);
+    PullAll(create_expand, *dba, symbol_table);
     dba->advance_command();
 
     EXPECT_EQ(CountIterable(dba->vertices()) - before_v,
@@ -412,7 +411,7 @@ TEST(Interpreter, MatchCreateNode) {
   auto create_node = std::make_shared<CreateNode>(m, std::get<1>(n_scan_all));
 
   EXPECT_EQ(CountIterable(dba->vertices()), 3);
-  ExecuteCreate(create_node, *dba, symbol_table);
+  PullAll(create_node, *dba, symbol_table);
   dba->advance_command();
   EXPECT_EQ(CountIterable(dba->vertices()), 6);
 }
@@ -457,7 +456,7 @@ TEST(Interpreter, MatchCreateExpand) {
 
     auto create_expand = std::make_shared<CreateExpand>(
         m, r, std::get<1>(n_scan_all), n_sym, cycle);
-    ExecuteCreate(create_expand, *dba, symbol_table);
+    PullAll(create_expand, *dba, symbol_table);
     dba->advance_command();
 
     EXPECT_EQ(CountIterable(dba->vertices()) - before_v,
@@ -687,221 +686,117 @@ TEST(Interpreter, EdgeFilterMultipleTypes) {
   EXPECT_EQ(result.GetResults().size(), 2);
 }
 
-struct NoContextExpressionEvaluator {
-  NoContextExpressionEvaluator() {}
-  Frame frame{0};
+TEST(Interpreter, Delete) {
+  Dbms dbms;
+  auto dba = dbms.active();
+
+  // make a fully-connected (one-direction, no cycles) with 4 nodes
+  std::vector<VertexAccessor> vertices;
+  for (int i = 0; i < 4; ++i) vertices.push_back(dba->insert_vertex());
+  auto type = dba->edge_type("type");
+  for (int j = 0; j < 4; ++j)
+    for (int k = j + 1; k < 4; ++k)
+      dba->insert_edge(vertices[j], vertices[k], type);
+
+  dba->advance_command();
+  EXPECT_EQ(4, CountIterable(dba->vertices()));
+  EXPECT_EQ(6, CountIterable(dba->edges()));
+
+  AstTreeStorage storage;
   SymbolTable symbol_table;
-  ExpressionEvaluator eval{frame, symbol_table};
-};
 
-TEST(ExpressionEvaluator, OrOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<OrOperator>(storage.Create<Literal>(true),
-                                        storage.Create<Literal>(false));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<OrOperator>(storage.Create<Literal>(true),
-                                  storage.Create<Literal>(true));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  // attempt to delete a vertex, and fail
+  {
+    auto n = MakeScanAll(storage, symbol_table, "n");
+    auto n_get = storage.Create<Identifier>("n");
+    symbol_table[*n_get] = std::get<2>(n);
+    auto delete_op = std::make_shared<plan::Delete>(
+        std::get<1>(n), std::vector<Expression *>{n_get}, false);
+    EXPECT_THROW(PullAll(delete_op, *dba, symbol_table), QueryRuntimeException);
+    dba->advance_command();
+    EXPECT_EQ(4, CountIterable(dba->vertices()));
+    EXPECT_EQ(6, CountIterable(dba->edges()));
+  }
+
+  // detach delete a single vertex
+  {
+    auto n = MakeScanAll(storage, symbol_table, "n");
+    auto n_get = storage.Create<Identifier>("n");
+    symbol_table[*n_get] = std::get<2>(n);
+    auto delete_op = std::make_shared<plan::Delete>(
+        std::get<1>(n), std::vector<Expression *>{n_get}, true);
+    Frame frame(symbol_table.max_position());
+    delete_op->MakeCursor(*dba)->Pull(frame, symbol_table);
+    dba->advance_command();
+    EXPECT_EQ(3, CountIterable(dba->vertices()));
+    EXPECT_EQ(3, CountIterable(dba->edges()));
+  }
+
+  // delete all remaining edges
+  {
+    auto n = MakeScanAll(storage, symbol_table, "n");
+    auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n),
+                          "r", EdgeAtom::Direction::RIGHT, false, "m", false);
+    auto r_get = storage.Create<Identifier>("r");
+    symbol_table[*r_get] = std::get<1>(r_m);
+    auto delete_op = std::make_shared<plan::Delete>(
+        std::get<4>(r_m), std::vector<Expression *>{r_get}, false);
+    PullAll(delete_op, *dba, symbol_table);
+    dba->advance_command();
+    EXPECT_EQ(3, CountIterable(dba->vertices()));
+    EXPECT_EQ(0, CountIterable(dba->edges()));
+  }
+
+  // delete all remaining vertices
+  {
+    auto n = MakeScanAll(storage, symbol_table, "n");
+    auto n_get = storage.Create<Identifier>("n");
+    symbol_table[*n_get] = std::get<2>(n);
+    auto delete_op = std::make_shared<plan::Delete>(
+        std::get<1>(n), std::vector<Expression *>{n_get}, false);
+    PullAll(delete_op, *dba, symbol_table);
+    dba->advance_command();
+    EXPECT_EQ(0, CountIterable(dba->vertices()));
+    EXPECT_EQ(0, CountIterable(dba->edges()));
+  }
 }
 
-TEST(ExpressionEvaluator, XorOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<XorOperator>(storage.Create<Literal>(true),
-                                         storage.Create<Literal>(false));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<XorOperator>(storage.Create<Literal>(true),
-                                   storage.Create<Literal>(true));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-}
+TEST(Interpreter, DeleteReturn) {
+  Dbms dbms;
+  auto dba = dbms.active();
 
-TEST(ExpressionEvaluator, AndOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<AndOperator>(storage.Create<Literal>(true),
-                                         storage.Create<Literal>(true));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<AndOperator>(storage.Create<Literal>(false),
-                                   storage.Create<Literal>(true));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-}
+  // make a fully-connected (one-direction, no cycles) with 4 nodes
+  auto prop = dba->property("prop");
+  for (int i = 0; i < 4; ++i) {
+    auto va = dba->insert_vertex();
+    va.PropsSet(prop, 42);
+  }
 
-TEST(ExpressionEvaluator, AdditionOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<AdditionOperator>(storage.Create<Literal>(2),
-                                              storage.Create<Literal>(3));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
-}
+  dba->advance_command();
+  EXPECT_EQ(4, CountIterable(dba->vertices()));
+  EXPECT_EQ(0, CountIterable(dba->edges()));
 
-TEST(ExpressionEvaluator, SubtractionOperator) {
   AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<SubtractionOperator>(storage.Create<Literal>(2),
-                                                 storage.Create<Literal>(3));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -1);
-}
+  SymbolTable symbol_table;
 
-TEST(ExpressionEvaluator, MultiplicationOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<MultiplicationOperator>(storage.Create<Literal>(2),
-                                                    storage.Create<Literal>(3));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 6);
-}
+  auto n = MakeScanAll(storage, symbol_table, "n");
 
-TEST(ExpressionEvaluator, DivisionOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<DivisionOperator>(storage.Create<Literal>(50),
-                                              storage.Create<Literal>(10));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
-}
+  auto n_get = storage.Create<Identifier>("n");
+  symbol_table[*n_get] = std::get<2>(n);
+  auto delete_op = std::make_shared<plan::Delete>(
+      std::get<1>(n), std::vector<Expression *>{n_get}, true);
 
-TEST(ExpressionEvaluator, ModOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<ModOperator>(storage.Create<Literal>(65),
-                                         storage.Create<Literal>(10));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
-}
+  auto prop_lookup =
+      storage.Create<PropertyLookup>(storage.Create<Identifier>("n"), prop);
+  symbol_table[*prop_lookup->expression_] = std::get<2>(n);
+  auto n_p = storage.Create<NamedExpression>("n", prop_lookup);
+  symbol_table[*n_p] = symbol_table.CreateSymbol("bla");
+  auto produce = MakeProduce(delete_op, n_p);
 
-TEST(ExpressionEvaluator, EqualOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<EqualOperator>(storage.Create<Literal>(10),
-                                           storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<EqualOperator>(storage.Create<Literal>(15),
-                                     storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<EqualOperator>(storage.Create<Literal>(20),
-                                     storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-}
-
-TEST(ExpressionEvaluator, NotEqualOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<NotEqualOperator>(storage.Create<Literal>(10),
-                                              storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<NotEqualOperator>(storage.Create<Literal>(15),
-                                        storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<NotEqualOperator>(storage.Create<Literal>(20),
-                                        storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-}
-
-TEST(ExpressionEvaluator, LessOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<LessOperator>(storage.Create<Literal>(10),
-                                          storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<LessOperator>(storage.Create<Literal>(15),
-                                    storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<LessOperator>(storage.Create<Literal>(20),
-                                    storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-}
-
-TEST(ExpressionEvaluator, GreaterOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<GreaterOperator>(storage.Create<Literal>(10),
-                                             storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<GreaterOperator>(storage.Create<Literal>(15),
-                                       storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<GreaterOperator>(storage.Create<Literal>(20),
-                                       storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-}
-
-TEST(ExpressionEvaluator, LessEqualOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<LessEqualOperator>(storage.Create<Literal>(10),
-                                               storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<LessEqualOperator>(storage.Create<Literal>(15),
-                                         storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<LessEqualOperator>(storage.Create<Literal>(20),
-                                         storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-}
-
-TEST(ExpressionEvaluator, GreaterEqualOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(10),
-                                                  storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
-  op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(15),
-                                            storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-  op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(20),
-                                            storage.Create<Literal>(15));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-}
-
-TEST(ExpressionEvaluator, NotOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<NotOperator>(storage.Create<Literal>(false));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
-}
-
-TEST(ExpressionEvaluator, UnaryPlusOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<UnaryPlusOperator>(storage.Create<Literal>(5));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
-}
-
-TEST(ExpressionEvaluator, UnaryMinusOperator) {
-  AstTreeStorage storage;
-  NoContextExpressionEvaluator eval;
-  auto *op = storage.Create<UnaryMinusOperator>(storage.Create<Literal>(5));
-  op->Accept(eval.eval);
-  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -5);
+  auto result = CollectProduce(produce, symbol_table, *dba);
+  EXPECT_EQ(4, result.GetResults().size());
+  dba->advance_command();
+  EXPECT_EQ(0, CountIterable(dba->vertices()));
 }
 
 TEST(Interpreter, Filter) {
diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp
index e5ce3fb8b..be350b52c 100644
--- a/tests/unit/query_common.hpp
+++ b/tests/unit/query_common.hpp
@@ -111,12 +111,12 @@ auto GetReturn(AstTreeStorage &storage,
 #define EDGE(...) query::test_common::GetEdge(storage, __VA_ARGS__)
 #define PATTERN(...) query::test_common::GetPattern(storage, {__VA_ARGS__})
 #define MATCH(...) \
-  query::test_common::GetWithPatterns<Match>(storage, {__VA_ARGS__})
+  query::test_common::GetWithPatterns<query::Match>(storage, {__VA_ARGS__})
 #define CREATE(...) \
-  query::test_common::GetWithPatterns<Create>(storage, {__VA_ARGS__})
-#define IDENT(name) storage.Create<Identifier>((name))
-#define LITERAL(val) storage.Create<Literal>((val))
+  query::test_common::GetWithPatterns<query::Create>(storage, {__VA_ARGS__})
+#define IDENT(name) storage.Create<query::Identifier>((name))
+#define LITERAL(val) storage.Create<query::Literal>((val))
 #define PROPERTY_LOOKUP(...) query::test_common::GetPropertyLookup(storage, __VA_ARGS__)
-#define NEXPR(name, expr) storage.Create<NamedExpression>((name), (expr))
+#define NEXPR(name, expr) storage.Create<query::NamedExpression>((name), (expr))
 #define RETURN(...) query::test_common::GetReturn(storage, {__VA_ARGS__})
 #define QUERY(...) query::test_common::GetQuery(storage, {__VA_ARGS__})
diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp
new file mode 100644
index 000000000..4d9fee0b3
--- /dev/null
+++ b/tests/unit/query_expression_evaluator.cpp
@@ -0,0 +1,234 @@
+//
+// Copyright 2017 Memgraph
+// Created by Mislav Bradac on 27.03.17.
+//
+
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "query/frontend/ast/ast.hpp"
+#include "query/frontend/interpret/interpret.hpp"
+#include "query/frontend/opencypher/parser.hpp"
+
+using namespace query;
+
+struct NoContextExpressionEvaluator {
+  NoContextExpressionEvaluator() {}
+  Frame frame{0};
+  SymbolTable symbol_table;
+  ExpressionEvaluator eval{frame, symbol_table};
+};
+
+TEST(ExpressionEvaluator, OrOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<OrOperator>(storage.Create<Literal>(true),
+                                        storage.Create<Literal>(false));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<OrOperator>(storage.Create<Literal>(true),
+                                  storage.Create<Literal>(true));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+}
+
+TEST(ExpressionEvaluator, XorOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<XorOperator>(storage.Create<Literal>(true),
+                                         storage.Create<Literal>(false));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<XorOperator>(storage.Create<Literal>(true),
+                                   storage.Create<Literal>(true));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+}
+
+TEST(ExpressionEvaluator, AndOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<AndOperator>(storage.Create<Literal>(true),
+                                         storage.Create<Literal>(true));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<AndOperator>(storage.Create<Literal>(false),
+                                   storage.Create<Literal>(true));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+}
+
+TEST(ExpressionEvaluator, AdditionOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<AdditionOperator>(storage.Create<Literal>(2),
+                                              storage.Create<Literal>(3));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
+}
+
+TEST(ExpressionEvaluator, SubtractionOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<SubtractionOperator>(storage.Create<Literal>(2),
+                                                 storage.Create<Literal>(3));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -1);
+}
+
+TEST(ExpressionEvaluator, MultiplicationOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<MultiplicationOperator>(storage.Create<Literal>(2),
+                                                    storage.Create<Literal>(3));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 6);
+}
+
+TEST(ExpressionEvaluator, DivisionOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<DivisionOperator>(storage.Create<Literal>(50),
+                                              storage.Create<Literal>(10));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
+}
+
+TEST(ExpressionEvaluator, ModOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<ModOperator>(storage.Create<Literal>(65),
+                                         storage.Create<Literal>(10));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
+}
+
+TEST(ExpressionEvaluator, EqualOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<EqualOperator>(storage.Create<Literal>(10),
+                                           storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<EqualOperator>(storage.Create<Literal>(15),
+                                     storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<EqualOperator>(storage.Create<Literal>(20),
+                                     storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+}
+
+TEST(ExpressionEvaluator, NotEqualOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<NotEqualOperator>(storage.Create<Literal>(10),
+                                              storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<NotEqualOperator>(storage.Create<Literal>(15),
+                                        storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<NotEqualOperator>(storage.Create<Literal>(20),
+                                        storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+}
+
+TEST(ExpressionEvaluator, LessOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<LessOperator>(storage.Create<Literal>(10),
+                                          storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<LessOperator>(storage.Create<Literal>(15),
+                                    storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<LessOperator>(storage.Create<Literal>(20),
+                                    storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+}
+
+TEST(ExpressionEvaluator, GreaterOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<GreaterOperator>(storage.Create<Literal>(10),
+                                             storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<GreaterOperator>(storage.Create<Literal>(15),
+                                       storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<GreaterOperator>(storage.Create<Literal>(20),
+                                       storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+}
+
+TEST(ExpressionEvaluator, LessEqualOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<LessEqualOperator>(storage.Create<Literal>(10),
+                                               storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<LessEqualOperator>(storage.Create<Literal>(15),
+                                         storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<LessEqualOperator>(storage.Create<Literal>(20),
+                                         storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+}
+
+TEST(ExpressionEvaluator, GreaterEqualOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(10),
+                                                  storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
+  op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(15),
+                                            storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+  op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(20),
+                                            storage.Create<Literal>(15));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+}
+
+TEST(ExpressionEvaluator, NotOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<NotOperator>(storage.Create<Literal>(false));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
+}
+
+TEST(ExpressionEvaluator, UnaryPlusOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<UnaryPlusOperator>(storage.Create<Literal>(5));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
+}
+
+TEST(ExpressionEvaluator, UnaryMinusOperator) {
+  AstTreeStorage storage;
+  NoContextExpressionEvaluator eval;
+  auto *op = storage.Create<UnaryMinusOperator>(storage.Create<Literal>(5));
+  op->Accept(eval.eval);
+  ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -5);
+}
diff --git a/tests/unit/query_planner.cpp b/tests/unit/query_planner.cpp
index 0a3d03047..e277d970d 100644
--- a/tests/unit/query_planner.cpp
+++ b/tests/unit/query_planner.cpp
@@ -11,8 +11,11 @@
 
 #include "query_common.hpp"
 
-using namespace query;
-using Direction = EdgeAtom::Direction;
+using namespace query::plan;
+using query::AstTreeStorage;
+using query::SymbolTable;
+using query::SymbolGenerator;
+using Direction = query::EdgeAtom::Direction;
 
 namespace {