From 18e7394d9e88a459789babe7bb40d16bbb674e4e Mon Sep 17 00:00:00 2001
From: Marko Budiselic <mbudiselicbuda@gmail.com>
Date: Sun, 3 Jul 2016 01:02:42 +0100
Subject: [PATCH] basic queries are manually implemented inside
 src/query_engine/main_queries

---
 CMakeLists.txt                                |  18 +-
 README.md                                     |  25 +++
 src/query_engine/main_queries.cpp             | 198 ++++++++++++++++--
 src/storage/edge_accessor.hpp                 |  10 +
 .../properties/traversers/consolewriter.hpp   |  59 ++++++
 5 files changed, 286 insertions(+), 24 deletions(-)
 create mode 100644 src/storage/model/properties/traversers/consolewriter.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 535633790..506dfe91b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,6 +110,8 @@ FILE(COPY ${src_dir}/query_engine/template DESTINATION ${CMAKE_BINARY_DIR})
 FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/compiled/cpu)
 
 # TODO: filter header files, all files don't need to be copied
+# they are all copied because query engine needs header files during
+# query compilation
 SUBDIRLIST(source_folders ${src_dir})
 foreach(source_folder ${source_folders})
     file(COPY ${src_dir}/${source_folder} DESTINATION ${build_include_dir})
@@ -176,14 +178,14 @@ EXECUTE_PROCESS(
 # target_link_libraries(query_engine cypher_lib)
 # target_link_libraries(query_engine ${fmt_static_lib})
 
-# # query hasher executable
-# add_executable(query_hasher src/query_engine/main_query_hasher.cpp)
-# target_link_libraries(query_hasher ${fmt_static_lib})
-# 
+# query hasher executable
+add_executable(query_hasher src/query_engine/main_query_hasher.cpp)
+target_link_libraries(query_hasher ${fmt_static_lib})
+
 # # hard coded implementation of queries
-# add_executable(queries src/query_engine/main_queries.cpp)
-# target_link_libraries(queries ${fmt_static_lib})
+add_executable(queries src/query_engine/main_queries.cpp)
+target_link_libraries(queries ${fmt_static_lib})
 
 # tests
-enable_testing()
-add_subdirectory(tests)
+# enable_testing()
+# add_subdirectory(tests)
diff --git a/README.md b/README.md
index 632cc5379..c62270f20 100644
--- a/README.md
+++ b/README.md
@@ -36,3 +36,28 @@ ctest -R concurrent --parallel 4
 # custom test build example
 clang++ -std=c++1y -o concurrent_skiplist ../tests/concurrent/skiplist.cpp -pg -I../src/ -I../libs/fmt -Lfmt-prefix/src/fmt-build/fmt -lfmt -lpthread
 
+# TODO
+* implement basic subset of queries:
+    * create node
+    * create edge
+    * find node
+    * find edge
+    * update node
+    * update edge
+    * delete node
+    * delete edge
+
+* implement index
+    * label index
+    * type index
+    * property index
+
+* from header only approach to .hpp + .cpp
+    * query engine has to be statically linked with the rest of the code
+
+* unit test of queries that are manually implemented
+    * src/query_engine/main_queries.cpp -> tests/unit/manual_queries
+
+* unit test of query_engine
+
+* console with history
diff --git a/src/query_engine/main_queries.cpp b/src/query_engine/main_queries.cpp
index 890bf0858..1c70a1ad0 100644
--- a/src/query_engine/main_queries.cpp
+++ b/src/query_engine/main_queries.cpp
@@ -6,62 +6,123 @@
 #include "utils/command_line/arguments.hpp"
 #include "cypher/common.hpp"
 #include "utils/time/timer.hpp"
+#include "storage/model/properties/traversers/consolewriter.hpp"
+
+using namespace std;
 
 // --
 // DESCRIPTION: create account
 // FULL:        CREATE (n:ACCOUNT {id: 50, name: "Nikola", country: "Croatia", created_at: 14634563}) RETURN n
 // STRIPPED:    CREATE(n:ACCOUNT{id:0,name:1,country:2,created_at:3})RETURNn
 // HASH:        10597108978382323595
+// STATUS:      DONE
 // --
 // DESCRIPTION: create personnel
 // FULL:        CREATE (n:PERSONNEL {id: 23, role: "CTO", created_at: 1235345}) RETURN n
 // STRIPPED:    CREATE(n:PERSONNEL{id:0,role:1,created_at:2})RETURNn
 // HASH:        4037885257628527960 
+// STATUS:      TODO
 // --
 // DESCRIPTION: create edge between ACCOUNT node and PERSONNEL node (IS type)
 // FULL:        MATCH (a:ACCOUNT {id:50}), (p:PERSONNEL {id: 23}) CREATE (a)-[:IS]->(p) 
 // STRIPPED:    MATCH(a:ACCOUNT{id:0}),(p:PERSONNEL{id:1})CREATE(a)-[:IS]->(p)
 // HASH:        16888190822925675190 
+// STATUS:      TODO
 // --
 // DESCRIPTION: find ACCOUNT node, PERSONNEL node and edge between them
 // FULL:        MATCH (a:ACCOUNT {id:50})-[r:IS]->(p:PERSONNEL {id: 23}) RETURN a,r,p 
 // STRIPPED:    MATCH(a:ACCOUNT{id:0})-[r:IS]->(p:PERSONNEL{id:1})RETURNa,r,p
 // HASH:        9672752533852902744 
+// STATUS:      TODO
 // --
 // DESCRIPTION: create OPPORTUNITY
 // FULL:         
 // STRIPPED:   
 // HASH:       
+// STATUS:      TODO
 // --
 // DESCRIPTION: create PERSONNEL-[:CREATED]->OPPORTUNITY
 // FULL:         
 // STRIPPED:   
 // HASH:       
+// STATUS:      TODO
 // --
 // DESCRIPTION: create COMPANY
 // FULL:         
 // STRIPPED:   
 // HASH:       
+// STATUS:      TODO
 // --
 // DESCRIPTION: create OPPORTUNITY-[:MATCH]->COMPANY
 // FULL:         
 // STRIPPED:   
 // HASH:       
+// STATUS:      TODO
+// --
+// DESCRIPTION: create an edge between two nodes that are found by the ID
+// FULL:        MATCH (a {id:0}), (p {id: 1}) CREATE (a)-[r:IS]->(p) RETURN r 
+// STRIPPED:    MATCH(a{id:0}),(p{id:1})CREATE(a)-[r:IS]->(p)RETURNr
+// HASH:        7939106225150551899 
+// STATUS:      DONE
 // --
-// DESCRIPTION: create edge between two nodes found by the ID
 // DESCRIPTION: fine node by the ID
+// FULL:        MATCH (n {id: 0}) RETURN n
+// STRIPPED:    MATCH(n{id:0})RETURNn
+// HASH:        11198568396549106428 
+// STATUS:      DONE
+// --
 // DESCRIPTION: find edge by the ID
+// FULL:        MATCH ()-[r]-() WHERE ID(r)=0 RETURN r
+// STRIPPED:    MATCH()-[r]-()WHEREID(r)=0RETURNr
+// HASH:        8320600413058284114       
+// STATUS:      DONE
+// --
+// DESCRIPTION: update node that is found by the ID
+// FULL:        MATCH (n: {id: 0}) SET n.name = "TEST100" RETURN n  
+// STRIPPED:    MATCH(n:{id:0})SETn.name=1RETURNn 
+// HASH:        6813335159006269041 
+// STATUS:      DONE
+// --
 // DESCRIPTION: find shortest path between two nodes specified by ID
+// FULL:         
+// STRIPPED:   
+// HASH:       
+// STATUS:      TODO
+// --
+// TODO: Labels and Types have to be extracted from a query during the
+// query compile time
+
+void cout_properties(const Properties& properties)
+{
+    ConsoleWriter writer;
+    properties.accept(writer);
+}
 
 int main(int argc, char** argv)
 {
     Db db;
-    std::map<uint64_t, std::function<void(const properties_t&)>> queries;
+    std::map<uint64_t, std::function<bool(const properties_t&)>> queries;
+
+    auto create_labeled_and_named_node = [&db](const properties_t& args)
+    {
+        auto& t = db.tx_engine.begin();
+        auto vertex_accessor = db.graph.vertices.insert(t);
+        vertex_accessor.property(
+            "name", args[0]
+        );
+        auto label = db.graph.label_store.find_or_create("LABEL");
+        vertex_accessor.add_label(label);
+        cout_properties(vertex_accessor.properties());
+        t.commit();
+
+        return true;
+    };
 
     auto create_account = [&db](const properties_t& args)
     {
         auto& t = db.tx_engine.begin();
         auto vertex_accessor = db.graph.vertices.insert(t);
+        // this can be a loop
         vertex_accessor.property(
             "id", args[0]
         );
@@ -74,35 +135,140 @@ int main(int argc, char** argv)
         vertex_accessor.property(
             "created_at", args[3]
         );
+        // here is the problem because LABEL can't be stripped out
         auto label = db.graph.label_store.find_or_create("ACCOUNT");
         vertex_accessor.add_label(label);
+        cout_properties(vertex_accessor.properties());
         t.commit();
-        // TODO: return the result once the bolt will be implemented
+
+        return true;
     };
 
-    auto find_node_by_id = [&db](const properties_t& args)
+    auto find_node_by_internal_id = [&db](const properties_t& args)
     {
         auto& t = db.tx_engine.begin();
-        auto id = static_cast<Int64&>(*args[0]);
+        auto id = static_cast<Int32&>(*args[0]);
         auto vertex_accessor = db.graph.vertices.find(t, Id(id.value));
+
+        if (!vertex_accessor) {
+            cout << "vertex doesn't exist" << endl;
+            t.commit();
+            return false; 
+        }
+
+        cout_properties(vertex_accessor.properties());
         t.commit();
-        // TODO: return the result once the bolt will be implemented
+
+        return true;
+    };
+
+    auto create_edge = [&db](const properties_t& args)
+    {
+        auto& t = db.tx_engine.begin();
+
+        auto v1 = db.graph.vertices.find(t, args[0]->as<Int32>().value);
+        if (!v1)
+            return t.commit(), false;
+
+        auto v2 = db.graph.vertices.find(t, args[1]->as<Int32>().value);
+        if (!v2)
+            return t.commit(), false;
+
+        auto e = db.graph.edges.insert(t);
+
+        v1.vlist->update(t)->data.out.add(e.vlist);
+        v2.vlist->update(t)->data.in.add(e.vlist);
+
+        e.from(v1.vlist);
+        e.to(v2.vlist);
+
+        e.edge_type(EdgeType("IN"));
+
+        t.commit();
+
+        cout << e.edge_type() << endl;
+        cout_properties(e.properties());
+
+        return true;
+    };
+
+    auto find_edge_by_internal_id = [&db](const properties_t& args)
+    {
+        auto& t = db.tx_engine.begin();
+        auto e = db.graph.edges.find(t, args[0]->as<Int32>().value);
+        if (!e)
+            return t.commit(), false;
+
+        // print edge type and properties
+        cout << "EDGE_TYPE: " << e.edge_type() << endl;
+
+        auto from = e.from();
+        cout << "FROM:" << endl;
+        cout_properties(from->find(t)->data.props);
+
+        auto to = e.to();
+        cout << "TO:" << endl;
+        cout_properties(to->find(t)->data.props);
+
+        t.commit();
+
+        return true;
+    };
+
+    auto update_node = [&db](const properties_t& args)
+    {
+        auto& t = db.tx_engine.begin();
+
+        auto v = db.graph.vertices.find(t, args[0]->as<Int32>().value);
+        if (!v)
+            return t.commit(), false;
+        
+        v.property("name", args[1]);
+        cout_properties(v.properties());
+
+        t.commit();
+
+        return true;
     };
 
     queries[10597108978382323595u] = create_account;
+    queries[5397556489557792025u]  = create_labeled_and_named_node;
+    queries[7939106225150551899u]  = create_edge;
+    queries[11198568396549106428u] = find_node_by_internal_id;
+    queries[8320600413058284114u]  = find_edge_by_internal_id;
+    queries[6813335159006269041u]  = update_node;
 
-    auto arguments = all_arguments(argc, argv);
-    auto input_query = extract_query(arguments);
-
+    // auto arguments = all_arguments(argc, argv);
+    // auto input_query = extract_query(arguments);
     auto stripper = make_query_stripper(TK_INT, TK_FLOAT, TK_STR, TK_BOOL);
-    auto stripped = stripper.strip(input_query);
-    
-    auto time = timer<ms>([&stripped, &queries]() {
-        for (int i = 0; i < 1000000; ++i) {
-            queries[stripped.hash](stripped.arguments);
+    // auto stripped = stripper.strip(input_query);
+    // 
+    // auto time = timer<ms>([&stripped, &queries]() {
+    //     for (int i = 0; i < 1000000; ++i) {
+    //         queries[stripped.hash](stripped.arguments);
+    //     }
+    // });
+    // std::cout << time << std::endl;
+
+    vector<string> history;
+    string command;
+    cout << "-- Memgraph query engine --" << endl;
+    do {
+
+        cout << "> ";
+        getline(cin, command);
+        history.push_back(command);
+        auto stripped = stripper.strip(command);
+
+        if (queries.find(stripped.hash) == queries.end()) {
+            cout << "unsupported query" << endl;
+            continue;
         }
-    });
-    std::cout << time << std::endl;
+    
+        auto result = queries[stripped.hash](stripped.arguments);
+        cout << "RETURN: " << result << endl;
+
+    } while (command != "quit");
 
     return 0;
 }
diff --git a/src/storage/edge_accessor.hpp b/src/storage/edge_accessor.hpp
index a24bd4a07..790a67b14 100644
--- a/src/storage/edge_accessor.hpp
+++ b/src/storage/edge_accessor.hpp
@@ -29,4 +29,14 @@ public:
     {
         this->record->data.to = vertex_record;
     }
+
+    auto from()
+    {
+        return this->record->data.from;
+    }
+
+    auto to()
+    {
+        return this->record->data.to;
+    }
 };
diff --git a/src/storage/model/properties/traversers/consolewriter.hpp b/src/storage/model/properties/traversers/consolewriter.hpp
new file mode 100644
index 000000000..4502d4be1
--- /dev/null
+++ b/src/storage/model/properties/traversers/consolewriter.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <iostream>
+
+#include "../properties.hpp"
+#include "../all.hpp"
+
+using std::cout;
+using std::endl;
+
+class ConsoleWriter
+{
+public:
+    ConsoleWriter() {}
+
+    void handle(const std::string& key, Property& value)
+    {
+        cout << "KEY: " << key << "; VALUE: ";
+
+        value.accept(*this);
+
+        cout << endl;
+    }
+
+    void handle(Bool& b)
+    {
+        cout << b.value();
+    }
+
+    void handle(String& s)
+    {
+        cout << s.value;
+    }
+
+    void handle(Int32& int32)
+    {
+        cout << int32.value;
+    }
+
+    void handle(Int64& int64)
+    {
+        cout << int64.value;
+    }
+
+    void handle(Float& f)
+    {
+        cout << f.value;
+    }
+
+    void handle(Double& d)
+    {
+        cout << d.value;
+    }
+
+    void finish()
+    {
+    }
+};
+