diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e454223e..8dfb771d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -344,10 +344,6 @@ set(memgraph_src_files
     ${src_dir}/mvcc/id.cpp
     ${src_dir}/durability/snapshooter.cpp
     ${src_dir}/durability/recovery.cpp
-#    ${src_dir}/snapshot/snapshot_engine.cpp
-#    ${src_dir}/snapshot/snapshoter.cpp
-#    ${src_dir}/snapshot/snapshot_encoder.cpp
-#    ${src_dir}/snapshot/snapshot_decoder.cpp
     ${src_dir}/storage/property_value.cpp
     ${src_dir}/storage/locking/record_lock.cpp
 #    ${src_dir}/storage/garbage/garbage.cpp
@@ -368,8 +364,9 @@ set(memgraph_src_files
     ${src_dir}/database/graph_db.cpp
     ${src_dir}/database/graph_db_accessor.cpp
     ${src_dir}/data_structures/concurrent/skiplist_gc.cpp
-    ${src_dir}/query/stripper.cpp
     ${src_dir}/query/engine.cpp
+    ${src_dir}/query/stripped.cpp
+    ${src_dir}/query/common.cpp
     ${src_dir}/query/console.cpp
     ${src_dir}/query/frontend/ast/cypher_main_visitor.cpp
     ${src_dir}/query/typed_value.cpp
diff --git a/src/communication/worker.hpp b/src/communication/worker.hpp
index ff7ea6b61..ce28c42f9 100644
--- a/src/communication/worker.hpp
+++ b/src/communication/worker.hpp
@@ -35,6 +35,7 @@ namespace communication {
  */
 template <typename Session, typename OutputStream, typename Socket>
 class Worker
+
     : public io::network::StreamReader<Worker<Session, OutputStream, Socket>,
                                        Session> {
   using StreamBuffer = io::network::StreamBuffer;
diff --git a/src/copy_hardcoded_queries.cpp b/src/copy_hardcoded_queries.cpp
index 67b3424a9..c5d9ce887 100644
--- a/src/copy_hardcoded_queries.cpp
+++ b/src/copy_hardcoded_queries.cpp
@@ -5,8 +5,9 @@
 #include <experimental/filesystem>
 namespace fs = std::experimental::filesystem;
 
+#include "logging/logger.hpp"
 #include "logging/streams/stdout.hpp"
-#include "query/preprocessor.hpp"
+#include "query/stripped.hpp"
 #include "utils/command_line/arguments.hpp"
 #include "utils/exceptions.hpp"
 #include "utils/file.hpp"
@@ -67,10 +68,9 @@ int main(int argc, char **argv) {
 
   auto src_files = utils::LoadFilePaths(src_path, "cpp");
 
-  QueryPreprocessor preprocessor;
   for (auto &src_file : src_files) {
     auto query = ExtractQuery(src_file);
-    auto query_hash = preprocessor.preprocess(query).hash;
+    auto query_hash = query::StrippedQuery(query).hash();
     auto dst_file = dst_path / fs::path(std::to_string(query_hash) + ".cpp");
     fs::copy(src_file, dst_file, fs::copy_options::overwrite_existing);
     logger.info("{} - (copy) -> {}", src_file, dst_file);
diff --git a/src/query/common.cpp b/src/query/common.cpp
new file mode 100644
index 000000000..2ebee4732
--- /dev/null
+++ b/src/query/common.cpp
@@ -0,0 +1,116 @@
+#include "query/common.hpp"
+
+#include <cctype>
+#include <codecvt>
+#include <locale>
+#include <stdexcept>
+
+#include "query/exceptions.hpp"
+#include "utils/assert.hpp"
+#include "utils/string.hpp"
+
+namespace query {
+
+int64_t ParseIntegerLiteral(const std::string &s) {
+  try {
+    // Not really correct since long long can have a bigger range than int64_t.
+    return static_cast<int64_t>(std::stoll(s, 0, 0));
+  } catch (const std::out_of_range &) {
+    throw SemanticException();
+  }
+}
+
+std::string ParseStringLiteral(const std::string &s) {
+  // This function is declared as lambda since its semantics is highly specific
+  // for this conxtext and shouldn't be used elsewhere.
+  auto EncodeEscapedUnicodeCodepoint = [](const std::string &s, int &i) {
+    int j = i + 1;
+    const int kShortUnicodeLength = 4;
+    const int kLongUnicodeLength = 8;
+    while (j < (int)s.size() - 1 && j < i + kLongUnicodeLength + 1 &&
+           isxdigit(s[j])) {
+      ++j;
+    }
+    if (j - i == kLongUnicodeLength + 1) {
+      char32_t t = stoi(s.substr(i + 1, kLongUnicodeLength), 0, 16);
+      i += kLongUnicodeLength;
+      std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
+      return converter.to_bytes(t);
+    } else if (j - i >= kShortUnicodeLength + 1) {
+      char16_t t = stoi(s.substr(i + 1, kShortUnicodeLength), 0, 16);
+      i += kShortUnicodeLength;
+      std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
+          converter;
+      return converter.to_bytes(t);
+    } else {
+      // This should never happen, except grammar changes and we don't notice
+      // change in this production.
+      debug_assert(false, "can't happen");
+      throw std::exception();
+    }
+  };
+
+  std::string unescaped;
+  bool escape = false;
+
+  // First and last char is quote, we don't need to look at them.
+  for (int i = 1; i < static_cast<int>(s.size()) - 1; ++i) {
+    if (escape) {
+      switch (s[i]) {
+        case '\\':
+          unescaped += '\\';
+          break;
+        case '\'':
+          unescaped += '\'';
+          break;
+        case '"':
+          unescaped += '"';
+          break;
+        case 'B':
+        case 'b':
+          unescaped += '\b';
+          break;
+        case 'F':
+        case 'f':
+          unescaped += '\f';
+          break;
+        case 'N':
+        case 'n':
+          unescaped += '\n';
+          break;
+        case 'R':
+        case 'r':
+          unescaped += '\r';
+          break;
+        case 'T':
+        case 't':
+          unescaped += '\t';
+          break;
+        case 'U':
+        case 'u':
+          unescaped += EncodeEscapedUnicodeCodepoint(s, i);
+          break;
+        default:
+          // This should never happen, except grammar changes and we don't
+          // notice change in this production.
+          debug_assert(false, "can't happen");
+          throw std::exception();
+      }
+      escape = false;
+    } else if (s[i] == '\\') {
+      escape = true;
+    } else {
+      unescaped += s[i];
+    }
+  }
+  return unescaped;
+}
+
+double ParseDoubleLiteral(const std::string &s) {
+  try {
+    return utils::ParseDouble(s);
+  } catch (const utils::BasicException &) {
+    throw SemanticException("Couldn't parse string to double");
+  }
+}
+}
diff --git a/src/query/common.hpp b/src/query/common.hpp
index fac6bef85..d354b68c7 100644
--- a/src/query/common.hpp
+++ b/src/query/common.hpp
@@ -1,7 +1,15 @@
 #pragma once
 
+#include <cstdint>
+#include <string>
+
 namespace query {
 
+// These are the functions for parsing literals from opepncypher query.
+int64_t ParseIntegerLiteral(const std::string &s);
+std::string ParseStringLiteral(const std::string &s);
+double ParseDoubleLiteral(const std::string &s);
+
 /**
  * Indicates that some part of query execution should
  * see the OLD graph state (the latest state before the
diff --git a/src/query/console.cpp b/src/query/console.cpp
index 9da6d9f62..07afa3acf 100644
--- a/src/query/console.cpp
+++ b/src/query/console.cpp
@@ -131,11 +131,13 @@ void query::Repl(Dbms &dbms) {
     // special commands
     if (command == "quit") break;
 
+    query::Interpreter interpeter;
+
     // regular cypher queries
     try {
       auto dba = dbms.active();
       ResultStreamFaker results;
-      query::Interpret(command, *dba, results);
+      interpeter.Interpret(command, *dba, results);
       PrintResults(results);
       dba->commit();
     } catch (const query::SyntaxException &e) {
diff --git a/src/query/engine.hpp b/src/query/engine.hpp
index a8ef23f7a..da4ecc522 100644
--- a/src/query/engine.hpp
+++ b/src/query/engine.hpp
@@ -11,7 +11,6 @@ namespace fs = std::experimental::filesystem;
 #include "query/interpreter.hpp"
 #include "query/plan_compiler.hpp"
 #include "query/plan_interface.hpp"
-#include "query/preprocessor.hpp"
 #include "utils/dynamic_lib.hpp"
 
 DECLARE_bool(INTERPRET);
@@ -47,9 +46,8 @@ class QueryEngine : public Loggable {
    * @return void
    */
   auto ReloadCustom(const std::string &query, const fs::path &plan_path) {
-    auto preprocessed = preprocessor.preprocess(query);
     Unload(query);
-    LoadCpp(plan_path, preprocessed.hash);
+    LoadCpp(plan_path, query::StrippedQuery(query).hash());
   }
 
   /**
@@ -67,16 +65,16 @@ class QueryEngine : public Loggable {
   auto Run(const std::string &query, GraphDbAccessor &db_accessor,
            Stream &stream) {
     if (FLAGS_INTERPRET) {
-      query::Interpret(query, db_accessor, stream);
+      interpreter_.Interpret(query, db_accessor, stream);
       return true;
     }
 
     clock_t start_time = clock();
-    auto preprocessed = preprocessor.preprocess(query);
+    query::StrippedQuery stripped(query);
     clock_t end_parsing_time = clock();
-    auto plan = LoadCypher(preprocessed);
+    auto plan = LoadCypher(stripped);
     clock_t end_planning_time = clock();
-    auto result = plan->run(db_accessor, preprocessed.arguments, stream);
+    auto result = plan->run(db_accessor, stripped.parameters(), stream);
     clock_t end_execution_time = clock();
     if (UNLIKELY(!result)) {
       // info because it might be something like deadlock in which
@@ -112,7 +110,7 @@ class QueryEngine : public Loggable {
    * return bool is the plan unloaded
    */
   auto Unload(const std::string &query) {
-    return query_plans.access().remove(preprocessor.preprocess(query).hash);
+    return query_plans_.access().remove(query::StrippedQuery(query).hash());
   }
 
   /**
@@ -123,8 +121,8 @@ class QueryEngine : public Loggable {
    * return bool
    */
   auto Loaded(const std::string &query) {
-    auto plans_accessor = query_plans.access();
-    return plans_accessor.find(preprocessor.preprocess(query).hash) !=
+    auto plans_accessor = query_plans_.access();
+    return plans_accessor.find(query::StrippedQuery(query).hash()) !=
            plans_accessor.end();
   }
 
@@ -134,9 +132,8 @@ class QueryEngine : public Loggable {
    * @return size_t the number of loaded query plans
    */
   auto Size() {  // TODO: const once whan ConcurrentMap::Accessor becomes const
-    return query_plans.access().size();
+    return query_plans_.access().size();
   }
-  // return query_plans.access().size(); }
 
  private:
   /**
@@ -147,29 +144,25 @@ class QueryEngine : public Loggable {
    *
    * @return runnable query plan
    */
-  auto LoadCypher(const StrippedQuery &stripped) {
-    auto plans_accessor = query_plans.access();
+  auto LoadCypher(const query::StrippedQuery &stripped) {
+    auto plans_accessor = query_plans_.access();
 
     // code is already compiled and loaded, just return runnable
     // instance
-    auto query_plan_it = plans_accessor.find(stripped.hash);
+    auto query_plan_it = plans_accessor.find(stripped.hash());
     if (query_plan_it != plans_accessor.end())
       return query_plan_it->second->instance();
 
     // find hardcoded query plan if exists
     auto hardcoded_path = fs::path(FLAGS_COMPILE_DIRECTORY + "hardcode/" +
-                                   std::to_string(stripped.hash) + ".cpp");
+                                   std::to_string(stripped.hash()) + ".cpp");
     if (fs::exists(hardcoded_path))
-      return LoadCpp(hardcoded_path, stripped.hash);
+      return LoadCpp(hardcoded_path, stripped.hash());
 
     // generate query plan
     auto generated_path = fs::path(FLAGS_COMPILE_DIRECTORY +
-                                   std::to_string(stripped.hash) + ".cpp");
-
-    query::frontend::opencypher::Parser parser(stripped.query);
-    // backend::cpp::Generator(parser.tree(), stripped.query, stripped.hash,
-    //                         generated_path);
-    return LoadCpp(generated_path, stripped.hash);
+                                   std::to_string(stripped.hash()) + ".cpp");
+    return LoadCpp(generated_path, stripped.hash());
   }
 
   /**
@@ -182,7 +175,7 @@ class QueryEngine : public Loggable {
    * @return runnable query plan
    */
   auto LoadCpp(const fs::path &path_cpp, const HashType hash) {
-    auto plans_accessor = query_plans.access();
+    auto plans_accessor = query_plans_.access();
 
     // code is already compiled and loaded, just return runnable
     // instance
@@ -199,7 +192,7 @@ class QueryEngine : public Loggable {
     auto path_so = FLAGS_COMPILE_DIRECTORY + std::to_string(hash) + "_" +
                    (std::string)Timestamp::now() + ".so";
 
-    plan_compiler.Compile(path_cpp, path_so);
+    PlanCompiler().Compile(path_cpp, path_so);
 
     auto query_plan = std::make_unique<QueryPlanLib>(path_so);
     // TODO: underlying object has to be live during query execution
@@ -212,7 +205,6 @@ class QueryEngine : public Loggable {
     return query_plan_instance;
   }
 
-  QueryPreprocessor preprocessor;
-  PlanCompiler plan_compiler;
-  ConcurrentMap<HashType, std::unique_ptr<QueryPlanLib>> query_plans;
+  query::Interpreter interpreter_;
+  ConcurrentMap<HashType, std::unique_ptr<QueryPlanLib>> query_plans_;
 };
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index 97924f025..e00962660 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "database/graph_db.hpp"
+#include "query/common.hpp"
 #include "query/exceptions.hpp"
 #include "query/interpret/awesome_memgraph_functions.hpp"
 #include "utils/assert.hpp"
@@ -823,122 +824,29 @@ antlrcpp::Any CypherMainVisitor::visitFunctionName(
 
 antlrcpp::Any CypherMainVisitor::visitDoubleLiteral(
     CypherParser::DoubleLiteralContext *ctx) {
-  try {
-    return utils::ParseDouble(ctx->getText());
-  } catch (const utils::BasicException &) {
-    throw SemanticException("Couldn't parse string to double");
-  }
+  return ParseDoubleLiteral(ctx->getText());
 }
 
 antlrcpp::Any CypherMainVisitor::visitIntegerLiteral(
     CypherParser::IntegerLiteralContext *ctx) {
-  try {
-    // Not really correct since long long can have a bigger range than int64_t.
-    return static_cast<int64_t>(std::stoll(ctx->getText(), 0, 0));
-  } catch (const std::out_of_range &) {
-    throw SemanticException();
-  }
+  return ParseIntegerLiteral(ctx->getText());
 }
 
 antlrcpp::Any CypherMainVisitor::visitStringLiteral(
     const std::string &escaped) {
-  // This function is declared as lambda since its semantics is highly specific
-  // for this conxtext and shouldn't be used elsewhere.
-  auto EncodeEscapedUnicodeCodepoint = [](const std::string &s, int &i) {
-    int j = i + 1;
-    const int kShortUnicodeLength = 4;
-    const int kLongUnicodeLength = 8;
-    while (j < (int)s.size() - 1 && j < i + kLongUnicodeLength + 1 &&
-           isxdigit(s[j])) {
-      ++j;
-    }
-    if (j - i == kLongUnicodeLength + 1) {
-      char32_t t = stoi(s.substr(i + 1, kLongUnicodeLength), 0, 16);
-      i += kLongUnicodeLength;
-      std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
-      return converter.to_bytes(t);
-    } else if (j - i >= kShortUnicodeLength + 1) {
-      char16_t t = stoi(s.substr(i + 1, kShortUnicodeLength), 0, 16);
-      i += kShortUnicodeLength;
-      std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
-          converter;
-      return converter.to_bytes(t);
-    } else {
-      // This should never happen, except grammar changes and we don't notice
-      // change in this production.
-      debug_assert(false, "can't happen");
-      throw std::exception();
-    }
-  };
-
-  std::string unescaped;
-  bool escape = false;
-
-  // First and last char is quote, we don't need to look at them.
-  for (int i = 1; i < (int)escaped.size() - 1; ++i) {
-    if (escape) {
-      switch (escaped[i]) {
-        case '\\':
-          unescaped += '\\';
-          break;
-        case '\'':
-          unescaped += '\'';
-          break;
-        case '"':
-          unescaped += '"';
-          break;
-        case 'B':
-        case 'b':
-          unescaped += '\b';
-          break;
-        case 'F':
-        case 'f':
-          unescaped += '\f';
-          break;
-        case 'N':
-        case 'n':
-          unescaped += '\n';
-          break;
-        case 'R':
-        case 'r':
-          unescaped += '\r';
-          break;
-        case 'T':
-        case 't':
-          unescaped += '\t';
-          break;
-        case 'U':
-        case 'u':
-          unescaped += EncodeEscapedUnicodeCodepoint(escaped, i);
-          break;
-        default:
-          // This should never happen, except grammar changes and we don't
-          // notice change in this production.
-          debug_assert(false, "can't happen");
-          throw std::exception();
-      }
-      escape = false;
-    } else if (escaped[i] == '\\') {
-      escape = true;
-    } else {
-      unescaped += escaped[i];
-    }
-  }
-  return unescaped;
+  return ParseStringLiteral(escaped);
 }
 
 antlrcpp::Any CypherMainVisitor::visitBooleanLiteral(
     CypherParser::BooleanLiteralContext *ctx) {
   if (ctx->getTokens(CypherParser::TRUE).size()) {
     return true;
-  } else if (ctx->getTokens(CypherParser::FALSE).size()) {
-    return false;
-  } else {
-    // This should never happen, except grammar changes and we don't
-    // notice change in this production.
-    debug_assert(false, "can't happen");
-    throw std::exception();
   }
+  if (ctx->getTokens(CypherParser::FALSE).size()) {
+    return false;
+  }
+  debug_assert(false, "Shouldn't happend");
+  throw std::exception();
 }
 
 antlrcpp::Any CypherMainVisitor::visitCypherDelete(
diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp
index e6ef7e5bf..a8aa09fa2 100644
--- a/src/query/interpreter.hpp
+++ b/src/query/interpreter.hpp
@@ -9,125 +9,133 @@
 #include "query/frontend/opencypher/parser.hpp"
 #include "query/frontend/semantic/symbol_generator.hpp"
 #include "query/interpret/frame.hpp"
-#include "query/plan/planner.hpp"
 #include "query/plan/cost_estimator.hpp"
+#include "query/plan/planner.hpp"
 
 namespace query {
 
-template <typename Stream>
-void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
-               Stream &stream) {
-  clock_t start_time = clock();
+class Interpreter {
+ public:
+  template <typename Stream>
+  void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
+                 Stream &stream) {
+    clock_t start_time = clock();
 
-  Config config;
-  Context ctx(config, db_accessor);
-  std::map<std::string, TypedValue> summary;
+    Config config;
+    Context ctx(config, db_accessor);
+    std::map<std::string, TypedValue> summary;
 
-  // query -> AST
-  frontend::opencypher::Parser parser(query);
+    // query -> AST
+    frontend::opencypher::Parser parser(query);
 
-  auto low_level_tree = parser.tree();
+    auto low_level_tree = parser.tree();
 
-  clock_t antlr_end_time = clock();
+    clock_t antlr_end_time = clock();
 
-  // AST -> high level tree
-  frontend::CypherMainVisitor visitor(ctx);
-  visitor.visit(low_level_tree);
-  auto high_level_tree = visitor.query();
+    // AST -> high level tree
+    frontend::CypherMainVisitor visitor(ctx);
+    visitor.visit(low_level_tree);
+    auto high_level_tree = visitor.query();
 
-  // symbol table fill
-  SymbolTable symbol_table;
-  SymbolGenerator symbol_generator(symbol_table);
-  high_level_tree->Accept(symbol_generator);
+    // symbol table fill
+    SymbolTable symbol_table;
+    SymbolGenerator symbol_generator(symbol_table);
+    high_level_tree->Accept(symbol_generator);
 
-  // high level tree -> logical plan
-  std::unique_ptr<plan::LogicalOperator> logical_plan;
-  double query_plan_cost_estimation = 0.0;
-  // TODO: Use gflags
-  bool FLAGS_query_cost_planner = true;
-  if (FLAGS_query_cost_planner) {
-    auto plans = plan::MakeLogicalPlan<plan::VariableStartPlanner>(
-        visitor.storage(), symbol_table, &db_accessor);
-    double min_cost = std::numeric_limits<double>::max();
-    for (auto &plan : plans) {
-      plan::CostEstimator estimator(db_accessor);
-      plan->Accept(estimator);
-      auto cost = estimator.cost();
-      if (!logical_plan || cost < min_cost) {
-        // We won't be iterating over plans anymore, so it's ok to invalidate
-        // unique_ptrs inside.
-        logical_plan = std::move(plan);
-        min_cost = cost;
+    // high level tree -> logical plan
+    std::unique_ptr<plan::LogicalOperator> logical_plan;
+    double query_plan_cost_estimation = 0.0;
+    // TODO: Use gflags
+    bool FLAGS_query_cost_planner = true;
+    if (FLAGS_query_cost_planner) {
+      auto plans = plan::MakeLogicalPlan<plan::VariableStartPlanner>(
+          visitor.storage(), symbol_table, &db_accessor);
+      double min_cost = std::numeric_limits<double>::max();
+      for (auto &plan : plans) {
+        plan::CostEstimator estimator(db_accessor);
+        plan->Accept(estimator);
+        auto cost = estimator.cost();
+        if (!logical_plan || cost < min_cost) {
+          // We won't be iterating over plans anymore, so it's ok to invalidate
+          // unique_ptrs inside.
+          logical_plan = std::move(plan);
+          min_cost = cost;
+        }
       }
+      query_plan_cost_estimation = min_cost;
+    } else {
+      logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
+          visitor.storage(), symbol_table, &db_accessor);
+      plan::CostEstimator cost_estimator(db_accessor);
+      logical_plan->Accept(cost_estimator);
+      query_plan_cost_estimation = cost_estimator.cost();
     }
-    query_plan_cost_estimation = min_cost;
-  } else {
-    logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
-        visitor.storage(), symbol_table, &db_accessor);
-    plan::CostEstimator cost_estimator(db_accessor);
-    logical_plan->Accept(cost_estimator);
-    query_plan_cost_estimation = cost_estimator.cost();
+
+    // generate frame based on symbol table max_position
+    Frame frame(symbol_table.max_position());
+
+    clock_t planning_end_time = clock();
+
+    std::vector<std::string> header;
+    std::vector<Symbol> output_symbols(
+        logical_plan->OutputSymbols(symbol_table));
+    if (!output_symbols.empty()) {
+      // Since we have output symbols, this means that the query contains RETURN
+      // clause, so stream out the results.
+
+      // generate header
+      for (const auto &symbol : output_symbols) header.push_back(symbol.name());
+      stream.Header(header);
+
+      // stream out results
+      auto cursor = logical_plan->MakeCursor(db_accessor);
+      while (cursor->Pull(frame, symbol_table)) {
+        std::vector<TypedValue> values;
+        for (const auto &symbol : output_symbols)
+          values.emplace_back(frame[symbol]);
+        stream.Result(values);
+      }
+    } else if (dynamic_cast<plan::CreateNode *>(logical_plan.get()) ||
+               dynamic_cast<plan::CreateExpand *>(logical_plan.get()) ||
+               dynamic_cast<plan::SetProperty *>(logical_plan.get()) ||
+               dynamic_cast<plan::SetProperties *>(logical_plan.get()) ||
+               dynamic_cast<plan::SetLabels *>(logical_plan.get()) ||
+               dynamic_cast<plan::RemoveProperty *>(logical_plan.get()) ||
+               dynamic_cast<plan::RemoveLabels *>(logical_plan.get()) ||
+               dynamic_cast<plan::Delete *>(logical_plan.get()) ||
+               dynamic_cast<plan::Merge *>(logical_plan.get())) {
+      stream.Header(header);
+      auto cursor = logical_plan->MakeCursor(db_accessor);
+      while (cursor->Pull(frame, symbol_table)) continue;
+    } else {
+      throw QueryRuntimeException("Unknown top level LogicalOperator");
+    }
+
+    clock_t execution_end_time = clock();
+
+    // helper function for calculating time in seconds
+    auto time_second = [](clock_t start, clock_t end) {
+      return TypedValue(double(end - start) / CLOCKS_PER_SEC);
+    };
+
+    summary["query_parsing_time"] = time_second(start_time, antlr_end_time);
+    summary["query_planning_time"] =
+        time_second(antlr_end_time, planning_end_time);
+    summary["query_plan_execution_time"] =
+        time_second(planning_end_time, execution_end_time);
+    summary["query_cost_estimate"] = query_plan_cost_estimation;
+
+    // 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 use "rw" because something must be set, but it doesn't
+    // have to be correct (for Bolt clients)
+    summary["type"] = "rw";
+    stream.Summary(summary);
   }
 
-  // generate frame based on symbol table max_position
-  Frame frame(symbol_table.max_position());
-
-  clock_t planning_end_time = clock();
-
-  std::vector<std::string> header;
-  std::vector<Symbol> output_symbols(logical_plan->OutputSymbols(symbol_table));
-  if (!output_symbols.empty()) {
-    // Since we have output symbols, this means that the query contains RETURN
-    // clause, so stream out the results.
-
-    // generate header
-    for (const auto &symbol : output_symbols) header.push_back(symbol.name());
-    stream.Header(header);
-
-    // stream out results
-    auto cursor = logical_plan->MakeCursor(db_accessor);
-    while (cursor->Pull(frame, symbol_table)) {
-      std::vector<TypedValue> values;
-      for (const auto &symbol : output_symbols)
-        values.emplace_back(frame[symbol]);
-      stream.Result(values);
-    }
-  } else if (dynamic_cast<plan::CreateNode *>(logical_plan.get()) ||
-             dynamic_cast<plan::CreateExpand *>(logical_plan.get()) ||
-             dynamic_cast<plan::SetProperty *>(logical_plan.get()) ||
-             dynamic_cast<plan::SetProperties *>(logical_plan.get()) ||
-             dynamic_cast<plan::SetLabels *>(logical_plan.get()) ||
-             dynamic_cast<plan::RemoveProperty *>(logical_plan.get()) ||
-             dynamic_cast<plan::RemoveLabels *>(logical_plan.get()) ||
-             dynamic_cast<plan::Delete *>(logical_plan.get()) ||
-             dynamic_cast<plan::Merge *>(logical_plan.get())) {
-    stream.Header(header);
-    auto cursor = logical_plan->MakeCursor(db_accessor);
-    while (cursor->Pull(frame, symbol_table)) continue;
-  } else {
-    throw QueryRuntimeException("Unknown top level LogicalOperator");
-  }
-
-  clock_t execution_end_time = clock();
-
-  // helper function for calculating time in seconds
-  auto time_second = [](clock_t start, clock_t end) {
-    return TypedValue(double(end - start) / CLOCKS_PER_SEC);
-  };
-
-  summary["query_parsing_time"] = time_second(start_time, antlr_end_time);
-  summary["query_planning_time"] =
-      time_second(antlr_end_time, planning_end_time);
-  summary["query_plan_execution_time"] =
-      time_second(planning_end_time, execution_end_time);
-  summary["query_cost_estimate"] = query_plan_cost_estimation;
-  // TODO set summary['type'] based on transaction metadata
-  // the type can't be determined based only on top level LogicalOperator
-  // (for example MATCH DELETE RETURN will have Produce as it's top)
-  // for now always use "rw" because something must be set, but it doesn't
-  // have to be correct (for Bolt clients)
-  summary["type"] = "rw";
-  stream.Summary(summary);
-}
+ private:
+  //  ConcurrentMap<HashType, std::unique_ptr<QueryPlanLib>> query_plans;
+};
 
 }  // namespace query
diff --git a/src/query/parameters.hpp b/src/query/parameters.hpp
index 0f8568319..4ade6ef0b 100644
--- a/src/query/parameters.hpp
+++ b/src/query/parameters.hpp
@@ -57,7 +57,7 @@ struct Parameters {
   }
 
   /** Returns the number of arguments in this container */
-  const size_t Size() const { return storage_.size(); }
+  size_t Size() const { return storage_.size(); }
 
  private:
   std::map<std::string, query::TypedValue> storage_;
@@ -71,4 +71,4 @@ struct Parameters {
   }
 };
 
-#endif //MEMGRAPH_PARAMETERS_HPP
+#endif  // MEMGRAPH_PARAMETERS_HPP
diff --git a/src/query/preprocessor.hpp b/src/query/preprocessor.hpp
deleted file mode 100644
index d2b96eab3..000000000
--- a/src/query/preprocessor.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include "logging/loggable.hpp"
-#include "query/stripper.hpp"
-
-/*
- * Query preprocessing contains:
- *     * query stripping
- *
- * This class is here because conceptually process of query preprocessing
- * might have more than one step + in current version of C++ standard
- * isn't trivial to instantiate QueryStripper because of template arguments +
- * it depends on underlying lexical analyser.
- *
- * The preprocessing results are:
- *     * stripped query      |
- *     * stripped arguments  |-> StrippedQuery
- *     * stripped query hash |
- */
-class QueryPreprocessor : public Loggable {
- public:
-  QueryPreprocessor() : Loggable("QueryPreprocessor") {}
-
-  /**
-   * Preprocess the query:
-   *     * strip parameters
-   *     * calculate query hash
-   *
-   * @param query that is going to be stripped
-   *
-   * @return QueryStripped object
-   */
-  auto preprocess(const std::string &query) {
-    auto preprocessed = query::Strip(query);
-    logger.info("stripped_query = {}", preprocessed.query);
-    logger.info("query_hash = {}", preprocessed.hash);
-
-    return preprocessed;
-  }
-};
diff --git a/src/query/stripped.cpp b/src/query/stripped.cpp
new file mode 100644
index 000000000..078500aa0
--- /dev/null
+++ b/src/query/stripped.cpp
@@ -0,0 +1,126 @@
+#include "query/stripped.hpp"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "antlr4-runtime.h"
+#include "logging/loggable.hpp"
+#include "query/common.hpp"
+#include "query/frontend/opencypher/generated/CypherBaseVisitor.h"
+#include "query/frontend/opencypher/generated/CypherLexer.h"
+#include "query/frontend/opencypher/generated/CypherParser.h"
+#include "utils/assert.hpp"
+#include "utils/hashing/fnv.hpp"
+#include "utils/string.hpp"
+
+using namespace antlropencypher;
+using namespace antlr4;
+
+namespace query {
+
+StrippedQuery::StrippedQuery(const std::string &query)
+    : Loggable("StrippedQuery") {
+  // Tokenize the query.
+  ANTLRInputStream input(query);
+  CypherLexer lexer(&input);
+  CommonTokenStream token_stream(&lexer);
+  token_stream.fill();
+  auto tokens = token_stream.getTokens();
+
+  // Initialize data structures we return.
+  std::vector<std::string> token_strings;
+  token_strings.reserve(tokens.size());
+
+  // A helper function that generates a new param name for the stripped
+  // literal, appends is to the the stripped_query and adds the passed
+  // value to stripped args.
+  auto replace_stripped = [this, &token_strings](const TypedValue &value) {
+    const auto &stripped_name = parameters_.Add(value);
+    token_strings.push_back("$" + stripped_name);
+  };
+
+  // Convert tokens to strings, perform lowercasing and filtering.
+  for (const auto *token : tokens) {
+    switch (token->getType()) {
+      case CypherLexer::UNION:
+      case CypherLexer::ALL:
+      case CypherLexer::OPTIONAL:
+      case CypherLexer::MATCH:
+      case CypherLexer::UNWIND:
+      case CypherLexer::AS:
+      case CypherLexer::MERGE:
+      case CypherLexer::ON:
+      case CypherLexer::CREATE:
+      case CypherLexer::SET:
+      case CypherLexer::DETACH:
+      case CypherLexer::DELETE:
+      case CypherLexer::REMOVE:
+      case CypherLexer::WITH:
+      case CypherLexer::DISTINCT:
+      case CypherLexer::RETURN:
+      case CypherLexer::ORDER:
+      case CypherLexer::BY:
+      case CypherLexer::L_SKIP:
+      case CypherLexer::LIMIT:
+      case CypherLexer::ASCENDING:
+      case CypherLexer::ASC:
+      case CypherLexer::DESCENDING:
+      case CypherLexer::DESC:
+      case CypherLexer::WHERE:
+      case CypherLexer::OR:
+      case CypherLexer::XOR:
+      case CypherLexer::AND:
+      case CypherLexer::NOT:
+      case CypherLexer::IN:
+      case CypherLexer::STARTS:
+      case CypherLexer::ENDS:
+      case CypherLexer::CONTAINS:
+      case CypherLexer::IS:
+      case CypherLexer::CYPHERNULL:
+      case CypherLexer::COUNT:
+      case CypherLexer::FILTER:
+      case CypherLexer::EXTRACT:
+      case CypherLexer::ANY:
+      case CypherLexer::NONE:
+      case CypherLexer::SINGLE:
+        token_strings.push_back(utils::ToLowerCase(token->getText()));
+        break;
+
+      case CypherLexer::SP:
+      case Token::EOF:
+        break;
+
+      case CypherLexer::DecimalInteger:
+      case CypherLexer::HexInteger:
+      case CypherLexer::OctalInteger:
+        replace_stripped(ParseIntegerLiteral(token->getText()));
+        break;
+
+      case CypherLexer::StringLiteral:
+        replace_stripped(ParseStringLiteral(token->getText()));
+        break;
+
+      case CypherLexer::RegularDecimalReal:
+      case CypherLexer::ExponentDecimalReal:
+        replace_stripped(ParseDoubleLiteral(token->getText()));
+        break;
+      case CypherLexer::TRUE:
+        replace_stripped(true);
+        break;
+      case CypherLexer::FALSE:
+        replace_stripped(false);
+        break;
+
+      default:
+        token_strings.push_back(token->getText());
+        break;
+    }
+  }
+
+  query_ = utils::Join(token_strings, " ");
+  hash_ = fnv(query_);
+  logger.info("stripped_query = {}", query_);
+  logger.info("query_hash = {}", hash_);
+}
+}
diff --git a/src/query/stripped.hpp b/src/query/stripped.hpp
index a3e423605..f09eaa427 100644
--- a/src/query/stripped.hpp
+++ b/src/query/stripped.hpp
@@ -2,24 +2,29 @@
 
 #include <map>
 
+#include "logging/loggable.hpp"
 #include "parameters.hpp"
 #include "storage/property_value_store.hpp"
 #include "utils/assert.hpp"
 #include "utils/hashing/fnv.hpp"
 
+namespace query {
+
 /*
 * StrippedQuery contains:
 *     * stripped query
 *     * plan arguments stripped from query
 *     * hash of stripped query
 */
-struct StrippedQuery {
-  StrippedQuery(const std::string &unstripped_query, const std::string &&query,
-                const Parameters &arguments, HashType hash)
-      : unstripped_query(unstripped_query),
-        query(query),
-        arguments(arguments),
-        hash(hash) {}
+class StrippedQuery : Loggable {
+ public:
+  /**
+   * Strips the input query and stores stripped query, stripped arguments and
+   * stripped query hash.
+   *
+   * @param query input query
+   */
+  explicit StrippedQuery(const std::string &query);
 
   /**
    * Copy constructor is deleted because we don't want to make unnecessary
@@ -35,15 +40,18 @@ struct StrippedQuery {
   StrippedQuery(StrippedQuery &&other) = default;
   StrippedQuery &operator=(StrippedQuery &&other) = default;
 
-  // original, unstripped query
-  const std::string unstripped_query;
+  const std::string &query() const { return query_; }
+  const Parameters &parameters() const { return parameters_; }
+  HashType hash() const { return hash_; }
 
+ private:
   // stripped query
-  const std::string query;
+  std::string query_;
 
   // striped arguments
-  const Parameters arguments;
+  Parameters parameters_;
 
   // hash based on the stripped query
-  const HashType hash;
+  HashType hash_;
 };
+}
diff --git a/src/query/stripper.cpp b/src/query/stripper.cpp
deleted file mode 100644
index b92cfffa8..000000000
--- a/src/query/stripper.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-//
-// Copyright 2017 Memgraph
-// Created by Florijan Stamenkovic on 07.03.17.
-//
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include "logging/loggable.hpp"
-#include "query/stripper.hpp"
-#include "utils/hashing/fnv.hpp"
-#include "utils/assert.hpp"
-
-#include "query/frontend/opencypher/generated/CypherBaseVisitor.h"
-#include "query/frontend/opencypher/generated/CypherLexer.h"
-#include "query/frontend/opencypher/generated/CypherParser.h"
-
-using namespace antlr4;
-using namespace antlropencypher;
-
-namespace query {
-
-/**
- * A visitor that for each literal that is not enclosed
- * by a range literal calls the given callback (which should
- * then replace Tokens of literals with placeholders).
- */
-class StripperVisitor : public antlropencypher::CypherBaseVisitor {
-public:
-  /**
-   * @param callback Callback function (see class description) called
-   *    with start and stop tokens of a literal.
-   */
-  StripperVisitor(const std::function<void(Token *, Token *)> callback)
-      : callback_(callback) {}
-
-  antlrcpp::Any visitRangeLiteral(
-      CypherParser::RangeLiteralContext *ctx) override {
-    is_in_range_ = true;
-    auto r_val = visitChildren(ctx);
-    is_in_range_ = false;
-    return r_val;
-  }
-
-  antlrcpp::Any visitLiteral(
-      CypherParser::LiteralContext *ctx) override {
-    if (ctx->booleanLiteral() != nullptr ||
-        ctx->StringLiteral() != nullptr ||
-        ctx->numberLiteral() != nullptr)
-      callback_(ctx->getStart(), ctx->getStop());
-
-    is_in_literal_ = true;
-    auto r_val = visitChildren(ctx);
-    is_in_literal_ = false;
-    return r_val;
-  }
-
-  antlrcpp::Any visitIntegerLiteral(
-      CypherParser::IntegerLiteralContext *ctx) override {
-    // convert integer literals into param placeholders only if not in range
-    // literal
-    if (!is_in_range_ && !is_in_literal_) callback_(ctx->getStart(), ctx->getStop());
-    return visitChildren(ctx);
-  }
-
-private:
-  const std::function<void(Token *, Token *)> callback_;
-  bool is_in_range_{false};
-  bool is_in_literal_{false};
-};
-
-/**
- * Strips the input query and returns stripped query, stripped arguments and
- * stripped query hash.
- *
- * @param query input query
- * @return stripped query, stripped arguments and stripped query hash as a
- *        single object of class StrippedQuery
- */
-StrippedQuery Strip(const std::string &query) {
-
-  // tokenize the query
-  ANTLRInputStream input(query);
-  CypherLexer lexer(&input);
-  CommonTokenStream token_stream(&lexer);
-  token_stream.fill();
-  std::vector<Token *> tokens = token_stream.getTokens();
-
-  // initialize data structures we return
-  Parameters stripped_arguments;
-
-  // convert tokens to strings, perform lowercasing and filtering
-  std::vector<std::string> token_strings;
-  token_strings.reserve(tokens.size());
-  for (int i = 0; i < tokens.size(); ++i)
-    switch (tokens[i]->getType()) {
-      case CypherLexer::UNION:
-      case CypherLexer::ALL:
-      case CypherLexer::OPTIONAL:
-      case CypherLexer::MATCH:
-      case CypherLexer::UNWIND:
-      case CypherLexer::AS:
-      case CypherLexer::MERGE:
-      case CypherLexer::ON:
-      case CypherLexer::CREATE:
-      case CypherLexer::SET:
-      case CypherLexer::DETACH:
-      case CypherLexer::DELETE:
-      case CypherLexer::REMOVE:
-      case CypherLexer::WITH:
-      case CypherLexer::DISTINCT:
-      case CypherLexer::RETURN:
-      case CypherLexer::ORDER:
-      case CypherLexer::BY:
-      case CypherLexer::L_SKIP:
-      case CypherLexer::LIMIT:
-      case CypherLexer::ASCENDING:
-      case CypherLexer::ASC:
-      case CypherLexer::DESCENDING:
-      case CypherLexer::DESC:
-      case CypherLexer::WHERE:
-      case CypherLexer::OR:
-      case CypherLexer::XOR:
-      case CypherLexer::AND:
-      case CypherLexer::NOT:
-      case CypherLexer::IN:
-      case CypherLexer::STARTS:
-      case CypherLexer::ENDS:
-      case CypherLexer::CONTAINS:
-      case CypherLexer::IS:
-      case CypherLexer::CYPHERNULL:
-      case CypherLexer::COUNT:
-      case CypherLexer::FILTER:
-      case CypherLexer::EXTRACT:
-      case CypherLexer::ANY:
-      case CypherLexer::NONE:
-      case CypherLexer::SINGLE:
-        token_strings.push_back(tokens[i]->getText());
-        std::transform(token_strings.back().begin(),
-                       token_strings.back().end(),
-                       token_strings.back().begin(), ::tolower);
-        break;
-
-      case CypherLexer::SP:
-      case Token::EOF:
-        token_strings.push_back("");
-        break;
-
-      default:
-        token_strings.push_back(tokens[i]->getText());
-        break;
-    }
-
-  // a helper function that generates a new param name for the stripped
-  // literal, appends is to the the stripped_query and adds the passed
-  // value to stripped args
-  auto replace_stripped = [&stripped_arguments, &token_strings](
-      const TypedValue &value, size_t token_position) {
-    const auto &stripped_name = stripped_arguments.Add(value);
-    token_strings[token_position] = "$" + stripped_name;
-  };
-
-  // callback for every literal that should be changed
-  // TODO consider literal parsing problems (like an int with 100 digits)
-  auto callback = [&replace_stripped](Token *start, Token *end) {
-    assert(start->getTokenIndex() == end->getTokenIndex());
-    switch (start->getType()) {
-      case CypherLexer::DecimalInteger:
-        replace_stripped(std::stoi(start->getText()),
-                         start->getTokenIndex());
-        break;
-      case CypherLexer::HexInteger:
-        replace_stripped(std::stoi(start->getText(), 0, 16),
-                         start->getTokenIndex());
-        break;
-      case CypherLexer::OctalInteger:
-        replace_stripped(std::stoi(start->getText(), 0, 8),
-                         start->getTokenIndex());
-        break;
-      case CypherLexer::StringLiteral:
-        replace_stripped(start->getText(),
-                         start->getTokenIndex());
-        break;
-      case CypherLexer::RegularDecimalReal:
-      case CypherLexer::ExponentDecimalReal:
-        replace_stripped(std::stof(start->getText()),
-                         start->getTokenIndex());
-        break;
-      case CypherLexer::TRUE:
-        replace_stripped(true, start->getTokenIndex());
-        break;
-      case CypherLexer::FALSE:
-        replace_stripped(false, start->getTokenIndex());
-        break;
-
-      default:
-        permanent_assert(true, "Unsupported literal type");
-    }
-  };
-
-  // parse the query and visit the AST with a stripping visitor
-  CypherParser parser(&token_stream);
-  tree::ParseTree *tree = parser.cypher();
-  StripperVisitor stripper_visitor(callback);
-  stripper_visitor.visit(tree);
-
-  // concatenate the stripped query tokens
-  std::string stripped_query;
-  stripped_query.reserve(query.size());
-  for (const std::string &token_string : token_strings) {
-    stripped_query += token_string;
-    if (token_string.size() > 0)
-      stripped_query += " ";
-  }
-
-  // return stripped query, stripped arguments and stripped query hash
-  return StrippedQuery(query,
-                       std::move(stripped_query),
-                       std::move(stripped_arguments),
-                       fnv(stripped_query));
-}
-};
diff --git a/src/query/stripper.hpp b/src/query/stripper.hpp
deleted file mode 100644
index f778e1eaa..000000000
--- a/src/query/stripper.hpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include "query/stripped.hpp"
-
-namespace query {
-StrippedQuery Strip(const std::string &query);
-};
diff --git a/src/utils/string.hpp b/src/utils/string.hpp
index 6673f32bc..72f6a5e71 100644
--- a/src/utils/string.hpp
+++ b/src/utils/string.hpp
@@ -59,11 +59,21 @@ inline std::string ToUpperCase(std::string s) {
  * Join strings in vector separated by a given separator.
  */
 inline std::string Join(const std::vector<std::string>& strings,
-                        const char* separator) {
-  std::ostringstream oss;
-  std::copy(strings.begin(), strings.end(),
-            std::ostream_iterator<std::string>(oss, separator));
-  return oss.str();
+                        const std::string& separator) {
+  if (strings.size() == 0U) return "";
+  int64_t total_size = 0;
+  for (const auto& x : strings) {
+    total_size += x.size();
+  }
+  total_size += separator.size() * (static_cast<int64_t>(strings.size()) - 1);
+  std::string s;
+  s.reserve(total_size);
+  s += strings[0];
+  for (auto it = strings.begin() + 1; it != strings.end(); ++it) {
+    s += separator;
+    s += *it;
+  }
+  return s;
 }
 
 /**
diff --git a/tests/benchmark/query/strip/stripper.cpp b/tests/benchmark/query/strip/stripper.cpp
index dc3639c8a..aa75af32d 100644
--- a/tests/benchmark/query/strip/stripper.cpp
+++ b/tests/benchmark/query/strip/stripper.cpp
@@ -3,7 +3,7 @@
 #include "benchmark/benchmark_api.h"
 #include "logging/default.hpp"
 #include "logging/streams/stdout.hpp"
-#include "query/preprocessor.hpp"
+#include "query/stripped.hpp"
 #include "yaml-cpp/yaml.h"
 
 auto BM_Strip = [](benchmark::State &state, auto &function, std::string query) {
@@ -22,10 +22,9 @@ int main(int argc, char **argv) {
   YAML::Node dataset = YAML::LoadFile(
       "../../tests/data/cypher_queries/stripper/query_dict.yaml");
 
-  QueryPreprocessor processor;
-  using std::placeholders::_1;
-  std::function<StrippedQuery(const std::string &query)> preprocess =
-      std::bind(&QueryPreprocessor::preprocess, &processor, _1);
+  auto preprocess = [](const std::string &query) {
+    return query::StrippedQuery(query);
+  };
 
   auto tests = dataset["benchmark_queries"].as<std::vector<std::string>>();
   for (auto &test : tests) {
diff --git a/tests/integration/query_engine_common.hpp b/tests/integration/query_engine_common.hpp
index 99a52582a..eded78dda 100644
--- a/tests/integration/query_engine_common.hpp
+++ b/tests/integration/query_engine_common.hpp
@@ -8,7 +8,7 @@ namespace fs = std::experimental::filesystem;
 #include "logging/default.hpp"
 #include "logging/streams/stdout.cpp"
 #include "query/engine.hpp"
-#include "query/preprocessor.hpp"
+#include "query/stripped.hpp"
 #include "stream/print_record_stream.hpp"
 #include "utils/command_line/arguments.hpp"
 #include "utils/file.hpp"
@@ -50,14 +50,13 @@ auto LoadQueryHashes(Logger &log, const fs::path &path) {
   // the intention of following block is to get all hashes
   // for which query implementations have to be compiled
   // calculate all hashes from queries file
-  QueryPreprocessor preprocessor;
   // hashes calculated from all queries in queries file
   QueryHashesT query_hashes;
   // fill the above set
   auto queries = utils::ReadLines(path);
   for (auto &query : queries) {
     if (query.empty()) continue;
-    query_hashes.insert(preprocessor.preprocess(query).hash);
+    query_hashes.insert(query::StrippedQuery(query).hash());
   }
   permanent_assert(query_hashes.size() > 0,
                    "At least one hash has to be present");
@@ -78,7 +77,6 @@ auto LoadQueryHashes(Logger &log, const fs::path &path) {
 auto LoadQueryPlans(Logger &log, QueryEngineT &engine,
                     const QueryHashesT &query_hashes, const fs::path &path) {
   log.info("*** Load/compile needed query implementations ***");
-  QueryPreprocessor preprocessor;
   auto plan_paths = LoadFilePaths(path, "cpp");
   // query mark will be used to extract queries from files (because we want
   // to be independent to a query hash)
@@ -105,7 +103,7 @@ auto LoadQueryPlans(Logger &log, QueryEngineT &engine,
       // load/compile implementations only for the queries which are
       // contained in queries_file
       // it doesn't make sense to compile something which won't be runned
-      if (query_hashes.find(preprocessor.preprocess(query).hash) ==
+      if (query_hashes.find(query::StrippedQuery(query).hash()) ==
           query_hashes.end())
         continue;
       log.info("Path {} will be loaded.", plan_path.c_str());
diff --git a/tests/manual/query_engine.cpp b/tests/manual/query_engine.cpp
index 8823bf5bf..89a946ead 100644
--- a/tests/manual/query_engine.cpp
+++ b/tests/manual/query_engine.cpp
@@ -27,7 +27,6 @@ int main(int argc, char* argv[]) {
 
   // init watcher
   FSWatcher watcher;
-  QueryPreprocessor preprocessor;
 
   int i = 0;
   watcher.watch(
diff --git a/tests/manual/query_hash.cpp b/tests/manual/query_hash.cpp
index ebb045f12..d748488d5 100644
--- a/tests/manual/query_hash.cpp
+++ b/tests/manual/query_hash.cpp
@@ -3,7 +3,7 @@
 
 #include "logging/default.hpp"
 #include "logging/streams/stdout.hpp"
-#include "query/preprocessor.hpp"
+#include "query/stripped.hpp"
 #include "utils/command_line/arguments.hpp"
 #include "utils/type_discovery.hpp"
 
@@ -24,16 +24,15 @@ int main(int argc, char **argv) {
   auto query = GET_ARG("-q", "CREATE (n) RETURN n").get_string();
 
   // run preprocessing
-  QueryPreprocessor preprocessor;
-  auto preprocessed = preprocessor.preprocess(query);
+  query::StrippedQuery preprocessed(query);
 
   // print query, stripped query, hash and variable values (propertie values)
   std::cout << fmt::format("Query: {}\n", query);
-  std::cout << fmt::format("Stripped query: {}\n", preprocessed.query);
-  std::cout << fmt::format("Query hash: {}\n", preprocessed.hash);
+  std::cout << fmt::format("Stripped query: {}\n", preprocessed.query());
+  std::cout << fmt::format("Query hash: {}\n", preprocessed.hash());
   std::cout << fmt::format("Property values:\n");
-  for (int i = 0; i < static_cast<int>(preprocessed.arguments.Size()); ++i) {
-    fmt::format("    {}", preprocessed.arguments.At(i));
+  for (int i = 0; i < static_cast<int>(preprocessed.parameters().Size()); ++i) {
+    fmt::format("    {}", preprocessed.parameters().At(i));
   }
   std::cout << std::endl;
 
diff --git a/tests/manual/query_stripper_timing.cpp b/tests/manual/query_stripper_timing.cpp
index f60312998..807dad355 100644
--- a/tests/manual/query_stripper_timing.cpp
+++ b/tests/manual/query_stripper_timing.cpp
@@ -6,7 +6,7 @@
 #include <ctime>
 #include <iostream>
 
-#include "query/stripper.hpp"
+#include "query/stripped.hpp"
 
 int main(int argc, const char **a) {
   if (argc < 2) {
@@ -18,8 +18,9 @@ int main(int argc, const char **a) {
   const int REPEATS = 100;
 
   clock_t begin = clock();
-  for (int i = 0; i < REPEATS; ++i)
-    query::Strip(query);
+  for (int i = 0; i < REPEATS; ++i) {
+    query::StrippedQuery(std::string(query));
+  }
   clock_t end = clock();
 
   double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
diff --git a/tests/unit/query_stripper.cpp b/tests/unit/query_stripper.cpp
index 8b42cc193..1643a350e 100644
--- a/tests/unit/query_stripper.cpp
+++ b/tests/unit/query_stripper.cpp
@@ -5,10 +5,11 @@
 
 #include "gtest/gtest.h"
 
-#include "query/stripper.hpp"
+#include "query/stripped.hpp"
 #include "query/typed_value.hpp"
 
 using query::TypedValue;
+using query::StrippedQuery;
 
 void EXPECT_PROP_TRUE(const TypedValue& a) {
   EXPECT_TRUE(a.type() == TypedValue::Type::Bool && a.Value<bool>());
@@ -19,75 +20,78 @@ void EXPECT_PROP_EQ(const TypedValue& a, const TypedValue& b) {
 }
 
 TEST(QueryStripper, NoLiterals) {
-  StrippedQuery stripped = query::Strip("CREATE (n)");
-  EXPECT_EQ(stripped.arguments.Size(), 0);
-  EXPECT_EQ(stripped.query, "create ( n ) ");
+  StrippedQuery stripped("CREATE (n)");
+  EXPECT_EQ(stripped.parameters().Size(), 0);
+  EXPECT_EQ(stripped.query(), "create ( n )");
 }
 
 TEST(QueryStripper, DecimalInteger) {
-  StrippedQuery stripped = query::Strip("RETURN 42");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_EQ(stripped.arguments.At(0).Value<int64_t>(), 42);
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 42");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 42);
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, OctalInteger) {
-  StrippedQuery stripped = query::Strip("RETURN 010");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_EQ(stripped.arguments.At(0).Value<int64_t>(), 8);
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 010");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 8);
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, HexInteger) {
-  StrippedQuery stripped = query::Strip("RETURN 0xa");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_EQ(stripped.arguments.At(0).Value<int64_t>(), 10);
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 0xa");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 10);
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, RegularDecimal) {
-  StrippedQuery stripped = query::Strip("RETURN 42.3");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_FLOAT_EQ(stripped.arguments.At(0).Value<double>(), 42.3);
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 42.3");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_FLOAT_EQ(stripped.parameters().At(0).Value<double>(), 42.3);
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, ExponentDecimal) {
-  StrippedQuery stripped = query::Strip("RETURN 4e2");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_FLOAT_EQ(stripped.arguments.At(0).Value<double>(), 4e2);
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 4e2");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_FLOAT_EQ(stripped.parameters().At(0).Value<double>(), 4e2);
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, StringLiteral) {
-  StrippedQuery stripped = query::Strip("RETURN 'something'");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_EQ(stripped.arguments.At(0).Value<std::string>(), "'something'");
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN 'something'");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_EQ(stripped.parameters().At(0).Value<std::string>(), "something");
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, BoolLiteral) {
-  StrippedQuery stripped = query::Strip("RETURN true");
-  EXPECT_EQ(stripped.arguments.Size(), 1);
-  EXPECT_PROP_EQ(stripped.arguments.At(0), TypedValue(true));
-  EXPECT_EQ(stripped.query, "return $stripped_arg_0 ");
+  StrippedQuery stripped("RETURN true");
+  EXPECT_EQ(stripped.parameters().Size(), 1);
+  EXPECT_PROP_EQ(stripped.parameters().At(0), TypedValue(true));
+  EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
 }
 
 TEST(QueryStripper, ListLiteral) {
-  StrippedQuery stripped = query::Strip("MATCH (n) RETURN [n, n.prop]");
-  EXPECT_EQ(stripped.arguments.Size(), 0);
-  EXPECT_EQ(stripped.query, "match ( n ) return [ n , n . prop ] ");
+  StrippedQuery stripped("MATCH (n) RETURN [n, n.prop]");
+  EXPECT_EQ(stripped.parameters().Size(), 0);
+  EXPECT_EQ(stripped.query(), "match ( n ) return [ n , n . prop ]");
 }
 
 TEST(QueryStripper, MapLiteral) {
-  StrippedQuery stripped = query::Strip("MATCH (n) RETURN {val: n}");
-  EXPECT_EQ(stripped.arguments.Size(), 0);
-  EXPECT_EQ(stripped.query, "match ( n ) return { val : n } ");
+  StrippedQuery stripped("MATCH (n) RETURN {val: n}");
+  EXPECT_EQ(stripped.parameters().Size(), 0);
+  EXPECT_EQ(stripped.query(), "match ( n ) return { val : n }");
 }
 
 TEST(QueryStripper, RangeLiteral) {
-  StrippedQuery stripped = query::Strip("MATCH (n)-[*2..3]-() RETURN n");
-  EXPECT_EQ(stripped.arguments.Size(), 0);
-  EXPECT_EQ(stripped.query, "match ( n ) - [ * 2 .. 3 ] - ( ) return n ");
+  StrippedQuery stripped("MATCH (n)-[*2..3]-() RETURN n");
+  EXPECT_EQ(stripped.parameters().Size(), 2);
+  EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 2);
+  EXPECT_EQ(stripped.parameters().At(1).Value<int64_t>(), 3);
+  EXPECT_EQ(
+      stripped.query(),
+      "match ( n ) - [ * $stripped_arg_0 .. $stripped_arg_1 ] - ( ) return n");
 }
-