diff --git a/src/query/entry.hpp b/src/query/entry.hpp
index 59fab4238..e0abc3000 100644
--- a/src/query/entry.hpp
+++ b/src/query/entry.hpp
@@ -1,15 +1,38 @@
 #pragma once
 
-#include "query/frontend/interpret/interpret.hpp"
-#include "query/frontend/logical/planner.hpp"
-#include "query/context.hpp"
 #include "database/graph_db_accessor.hpp"
 #include "query/context.hpp"
+#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/typecheck/typecheck.hpp"
 
 namespace query {
 
+/**
+ * A Stream implementation that writes out to the
+ * console (for testing and debugging only).
+ */
+// TODO move somewhere to /test/manual or so
+class ConsoleResultStream : public Loggable {
+ public:
+  ConsoleResultStream() : Loggable("ConsoleResultStream") {}
+
+  void Header(const std::vector<std::string> &) { logger.info("header"); }
+
+  void Result(std::vector<TypedValue> &values) {
+    for (auto value : values) {
+      auto va = value.Value<VertexAccessor>();
+      logger.info("    {}", va.labels().size());
+    }
+  }
+
+  void Summary(const std::map<std::string, TypedValue> &) {
+    logger.info("summary");
+  }
+};
+
 template <typename Stream>
 class Engine {
  public:
@@ -38,18 +61,34 @@ class Engine {
     // generate frame based on symbol table max_position
     Frame frame(symbol_table.max_position());
 
-    // interpret
-    auto cursor = logical_plan->MakeCursor(db_accessor);
-    logical_plan->WriteHeader(stream);
-    auto symbols = logical_plan->OutputSymbols(symbol_table);
-    while (cursor->pull(frame, symbol_table)) {
-      std::vector<TypedValue> values;
-      for (auto symbol : symbols) {
-        values.emplace_back(frame[symbol.position_]);
+    auto *produce = dynamic_cast<Produce*>(logical_plan.get());
+
+    if (produce) {
+      // top level node in the operator tree is a produce (return)
+      // so stream out results
+
+      // generate header
+      std::vector<std::string> header;
+      for (auto named_expression : produce->named_expressions())
+        header.push_back(named_expression->name_);
+      stream.Header(header);
+
+      // collect the symbols from the return clause
+      std::vector<Symbol> symbols;
+      for (auto named_expression : produce->named_expressions())
+        symbols.emplace_back(symbol_table[*named_expression]);
+
+      // stream out results
+      auto cursor = produce->MakeCursor(db_accessor);
+      while (cursor->Pull(frame, symbol_table)) {
+        std::vector<TypedValue> values;
+        for (auto &symbol : symbols)
+          values.emplace_back(frame[symbol.position_]);
+        stream.Result(values);
       }
-      stream.Result(values);
+
+      stream.Summary({{std::string("type"), TypedValue("r")}});
     }
-    stream.Summary({{std::string("type"), TypedValue("r")}});
   }
 };
 }
diff --git a/src/query/frontend/logical/operator.hpp b/src/query/frontend/logical/operator.hpp
index 94f3793e7..204e71dc4 100644
--- a/src/query/frontend/logical/operator.hpp
+++ b/src/query/frontend/logical/operator.hpp
@@ -11,24 +11,6 @@
 
 namespace query {
 
-class ConsoleResultStream : public Loggable {
- public:
-  ConsoleResultStream() : Loggable("ConsoleResultStream") {}
-
-  void Header(const std::vector<std::string>&) { logger.info("header"); }
-
-  void Result(std::vector<TypedValue>& values) {
-    for (auto value : values) {
-      auto va = value.Value<VertexAccessor>();
-      logger.info("    {}", va.labels().size());
-    }
-  }
-
-  void Summary(const std::map<std::string, TypedValue>&) {
-    logger.info("summary");
-  }
-};
-
 class Cursor {
  public:
   virtual bool Pull(Frame&, SymbolTable&) = 0;
@@ -39,10 +21,6 @@ class LogicalOperator {
  public:
   auto children() { return children_; };
   virtual std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) = 0;
-  virtual void WriteHeader(ConsoleResultStream&) {}
-  virtual std::vector<Symbol> OutputSymbols(SymbolTable& symbol_table) {
-    return {};
-  }
   virtual ~LogicalOperator() {}
 
  protected:
@@ -106,22 +84,11 @@ class Produce : public LogicalOperator {
     children_.emplace_back(input);
   }
 
-  void WriteHeader(ConsoleResultStream& stream) override {
-    // TODO: write real result
-    stream.Header({"n"});
-  }
-
   std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
     return std::make_unique<ProduceCursor>(*this, db);
   }
 
-  std::vector<Symbol> OutputSymbols(SymbolTable& symbol_table) override {
-    std::vector<Symbol> result;
-    for (auto named_expr : named_expressions_) {
-      result.emplace_back(symbol_table[*named_expr]);
-    }
-    return result;
-  }
+  const auto& named_expressions() { return named_expressions_; }
 
  private:
   class ProduceCursor : public Cursor {