From d04cf101b46eb050aef4f7d3ef28da96f09028aa Mon Sep 17 00:00:00 2001 From: Marko Budiselic <mbudiselicbuda@gmail.com> Date: Sun, 31 Jul 2016 18:58:12 +0100 Subject: [PATCH] Cypher CRUD support is becomes bigger and bigger. Implemented: create relationship, match relationship and update relationship --- CMakeLists.txt | 48 +++++++++--- src/memgraph_bolt.cpp | 17 ++++ src/{memgraph.cpp => memgraph_http.cpp} | 0 .../code_generator/handlers/create.hpp | 77 +++++++++++++++++-- .../code_generator/handlers/match.hpp | 4 +- .../code_generator/handlers/set.hpp | 15 ++-- .../code_generator/query_action_data.hpp | 2 +- .../code_generator/structures.hpp | 2 +- src/query_engine/exceptions/errors.hpp | 7 ++ src/query_engine/hardcode/queries.hpp | 33 ++++++++ src/query_engine/traverser/code.hpp | 28 ++++--- src/query_engine/traverser/cpp_traverser.hpp | 50 +++++++----- 12 files changed, 222 insertions(+), 61 deletions(-) create mode 100644 src/memgraph_bolt.cpp rename src/{memgraph.cpp => memgraph_http.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index faf0da662..1337a7772 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,11 @@ project(${ProjectId}) find_package(Threads REQUIRED) +# flags # c++14 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y") +# glibcxx debug +# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG") # functions @@ -175,14 +178,13 @@ EXECUTE_PROCESS( ) # # memgraph executable -# add_executable(memgraph src/memgraph.cpp) -# add_dependencies(memgraph cypher_lib) -# # memgraph link libraries -# target_link_libraries(memgraph Threads::Threads) -# target_link_libraries(memgraph pcre) -# target_link_libraries(memgraph ${libuv_static_lib}) -# target_link_libraries(memgraph ${r3_static_lib}) -# target_link_libraries(memgraph ${http_parser_static_lib}) +# add_executable(memgraph_http src/memgraph.cpp) +# add_dependencies(memgraph_http cypher_lib) +# target_link_libraries(memgraph_http Threads::Threads) +# target_link_libraries(memgraph_http pcre) +# target_link_libraries(memgraph_http ${libuv_static_lib}) +# target_link_libraries(memgraph_http ${r3_static_lib}) +# target_link_libraries(memgraph_http ${http_parser_static_lib}) # # query_engine executable # add_executable(query_engine src/query_engine/main_query_engine.cpp) @@ -214,11 +216,33 @@ set(memgraph_src_files ${src_dir}/transactions/transaction.cpp ${src_dir}/template_engine/engine.cpp ) - -# hard coded implementation of queries -# add_executable(queries src/query_engine/main_queries.cpp ${memgraph_src_files}) -# target_link_libraries(queries ${fmt_static_lib}) +add_library(libmemgraph ${memgraph_src_files}) # tests enable_testing() add_subdirectory(tests) + +# memgraph build name +execute_process( + OUTPUT_VARIABLE COMMIT_NO + COMMAND git rev-list --count HEAD +) +execute_process( + OUTPUT_VARIABLE COMMIT_HASH + COMMAND git rev-parse --short HEAD +) +string(STRIP ${COMMIT_HASH} COMMIT_HASH) +string(STRIP ${COMMIT_NO} COMMIT_NO) +set(MEMGRAPH_BUILD_NAME "memgraph_${COMMIT_HASH}_${COMMIT_NO}") + +# DEBUG BUILD +add_executable(${MEMGRAPH_BUILD_NAME}_debug ${src_dir}/memgraph_bolt.cpp) + +# TEST BUILD +# TODO + +# O-OPTIMIZED BUILD +# TODO + +# RELEASE BUILD +# TODO diff --git a/src/memgraph_bolt.cpp b/src/memgraph_bolt.cpp new file mode 100644 index 000000000..fb59219de --- /dev/null +++ b/src/memgraph_bolt.cpp @@ -0,0 +1,17 @@ +#include "utils/terminate_handler.hpp" + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + std::cout << "Port not defined" << std::endl; + std::exit(0); + } + + auto port = std::stoi(argv[1]); + + std::cout << "Port is: " << port << std::endl; + + std::set_terminate(&terminate_handler); + + return 0; +} diff --git a/src/memgraph.cpp b/src/memgraph_http.cpp similarity index 100% rename from src/memgraph.cpp rename to src/memgraph_http.cpp diff --git a/src/query_engine/code_generator/handlers/create.hpp b/src/query_engine/code_generator/handlers/create.hpp index 29bd1e614..6863aa500 100644 --- a/src/query_engine/code_generator/handlers/create.hpp +++ b/src/query_engine/code_generator/handlers/create.hpp @@ -2,6 +2,23 @@ #include "query_engine/code_generator/handlers/includes.hpp" +using Direction = RelationshipData::Direction; + +auto update_properties(const QueryActionData &action_data, + const std::string &name) +{ + std::string code = ""; + + auto entity_data = action_data.get_entity_property(name); + for (auto &property : entity_data.properties) { + auto index = + action_data.parameter_index.at(ParameterIndexKey(name, property)); + code += LINE(fmt::format(code::set_property, name, property, index)); + } + + return code; +} + auto create_query_action = [](CypherStateData &cypher_data, const QueryActionData &action_data) -> std::string { @@ -11,26 +28,72 @@ auto create_query_action = for (auto const &kv : action_data.actions) { if (kv.second == ClauseAction::CreateNode) { - // 1. create node 2. update labels 3. update properties + // create node auto &name = kv.first; code += LINE(fmt::format(code::create_vertex, name)); + + // update properties + code += update_properties(action_data, name); + + // update labels auto entity_data = action_data.get_entity_property(name); - for (auto &property : entity_data.properties) { - auto index = action_data.parameter_index.at( - ParameterIndexKey(name, property)); - code += LINE(fmt::format(code::set_vertex_property, name, - property, index)); - } for (auto &label : entity_data.tags) { code += LINE(fmt::format(code::create_label, label)); code += LINE(fmt::format(code::add_label, name, label)); } + + // mark node as created cypher_data.node_created(name); } if (kv.second == ClauseAction::CreateRelationship) { + // create relationship auto name = kv.first; code += LINE(fmt::format(code::create_edge, name)); + + // update properties + code += update_properties(action_data, name); + + // update tag + auto entity_data = action_data.get_entity_property(name); + for (auto &tag : entity_data.tags) { + code += LINE(fmt::format(code::find_type, tag)); + code += LINE(fmt::format(code::set_type, name, tag)); + } + + // find start and end node + auto &relationships_data = action_data.relationship_data; + if (relationships_data.find(name) == relationships_data.end()) + throw CodeGenerationError("Unable to find data for: " + name); + auto &relationship_data = relationships_data.at(name); + auto left_node = relationship_data.nodes.first; + auto right_node = relationship_data.nodes.second; + + // TODO: If node isn't already matched or created it has to be + // created here. It is not possible for now. + if (cypher_data.status(left_node) != EntityStatus::Matched) { + throw SemanticError("Create Relationship: node " + left_node + + " can't be found"); + } + if (cypher_data.status(right_node) != EntityStatus::Matched) { + throw SemanticError("Create Relationship: node " + right_node + + " can't be found"); + } + + // define direction + if (relationship_data.direction == Direction::Right) { + code += LINE(fmt::format(code::node_out, left_node, name)); + code += LINE(fmt::format(code::node_in, right_node, name)); + code += LINE(fmt::format(code::edge_from, name, left_node)); + code += LINE(fmt::format(code::edge_to, name, right_node)); + } else if (relationship_data.direction == Direction::Left) { + code += LINE(fmt::format(code::node_out, right_node, name)); + code += LINE(fmt::format(code::node_in, left_node, name)); + code += LINE(fmt::format(code::edge_from, name, right_node)); + code += LINE(fmt::format(code::edge_to, name, left_node)); + } + + // mark relationship as created cypher_data.relationship_created(name); } } diff --git a/src/query_engine/code_generator/handlers/match.hpp b/src/query_engine/code_generator/handlers/match.hpp index 8b9e16603..47fa263e5 100644 --- a/src/query_engine/code_generator/handlers/match.hpp +++ b/src/query_engine/code_generator/handlers/match.hpp @@ -31,12 +31,12 @@ auto match_query_action = if (kv.second == ClauseAction::MatchNode) { auto name = kv.first; if (already_matched(cypher_data, name, EntityType::Node)) continue; + cypher_data.node_matched(name); auto place = action_data.csm.min(kv.first); if (place == entity_search::search_internal_id) { auto index = fetch_internal_index(action_data, name); code += LINE(fmt::format(code::match_vertex_by_id, name, index)); - cypher_data.node_matched(name); } } @@ -45,11 +45,11 @@ auto match_query_action = auto name = kv.first; if (already_matched(cypher_data, name, EntityType::Relationship)) continue; + cypher_data.relationship_matched(name); auto place = action_data.csm.min(kv.first); if (place == entity_search::search_internal_id) { auto index = fetch_internal_index(action_data, name); code += LINE(fmt::format(code::match_edge_by_id, name, index)); - cypher_data.relationship_matched(name); } } } diff --git a/src/query_engine/code_generator/handlers/set.hpp b/src/query_engine/code_generator/handlers/set.hpp index bce2cfa73..e1a582e42 100644 --- a/src/query_engine/code_generator/handlers/set.hpp +++ b/src/query_engine/code_generator/handlers/set.hpp @@ -9,16 +9,17 @@ auto set_query_action = [](CypherStateData &cypher_data, for (auto const &kv : action_data.actions) { auto name = kv.first; + if (kv.second == ClauseAction::UpdateNode && cypher_data.status(name) == EntityStatus::Matched && cypher_data.type(name) == EntityType::Node) { - auto entity_data = action_data.get_entity_property(name); - for (auto &property : entity_data.properties) { - auto index = action_data.parameter_index.at( - ParameterIndexKey(name, property)); - code += LINE( - fmt::format(code::update_property, name, property, index)); - } + code += update_properties(action_data, name); + } + + if (kv.second == ClauseAction::UpdateRelationship && + cypher_data.status(name) == EntityStatus::Matched && + cypher_data.type(name) == EntityType::Relationship) { + code += update_properties(action_data, name); } } diff --git a/src/query_engine/code_generator/query_action_data.hpp b/src/query_engine/code_generator/query_action_data.hpp index abcb805d3..7d45b9b01 100644 --- a/src/query_engine/code_generator/query_action_data.hpp +++ b/src/query_engine/code_generator/query_action_data.hpp @@ -76,7 +76,7 @@ struct ParameterIndexKey } }; -struct RelationshipData : public EntityData +struct RelationshipData { enum class Direction { diff --git a/src/query_engine/code_generator/structures.hpp b/src/query_engine/code_generator/structures.hpp index b4884c72b..f24247b94 100644 --- a/src/query_engine/code_generator/structures.hpp +++ b/src/query_engine/code_generator/structures.hpp @@ -14,6 +14,6 @@ public: { runtime_assert(this->size() > 1, "Array size shoud be bigger than 1"); - return std::make_pair(*(this->end() - 2), *(this->end() - 1)); + return std::make_pair(*(this->end() - 1), *(this->end() - 2)); } }; diff --git a/src/query_engine/exceptions/errors.hpp b/src/query_engine/exceptions/errors.hpp index 70f6d2ddb..8eb87318f 100644 --- a/src/query_engine/exceptions/errors.hpp +++ b/src/query_engine/exceptions/errors.hpp @@ -10,3 +10,10 @@ public: SemanticError(const std::string& what) : BasicException("Semantic error: " + what) {} }; + +class CodeGenerationError : public BasicException +{ +public: + CodeGenerationError(const std::string& what) : + BasicException("Code Generation error: " + what) {} +}; diff --git a/src/query_engine/hardcode/queries.hpp b/src/query_engine/hardcode/queries.hpp index 17fe9080f..c898f1350 100644 --- a/src/query_engine/hardcode/queries.hpp +++ b/src/query_engine/hardcode/queries.hpp @@ -16,6 +16,16 @@ auto load_queries(Db& db) { std::map<uint64_t, std::function<bool(const properties_t &)>> queries; + // CREATE (n {prop: 0}) RETURN n) + auto create_node = [&db](const properties_t &args) { + auto &t = db.tx_engine.begin(); + auto vertex_accessor = db.graph.vertices.insert(t); + vertex_accessor.property("prop", args[0]); + t.commit(); + return true; + }; + queries[11597417457737499503u] = create_node; + 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); @@ -137,6 +147,28 @@ auto load_queries(Db& db) return true; }; + // MATCH (n1), (n2) WHERE ID(n1)=0 AND ID(n2)=1 CREATE (n1)<-[r:IS {age: 25, weight: 70}]-(n2) RETURN r] + auto create_edge_v2 = [&db](const properties_t &args) + { + auto& t = db.tx_engine.begin(); + auto n1 = db.graph.vertices.find(t, args[0]->as<Int64>().value); + if (!n1) return t.commit(), false; + auto n2 = db.graph.vertices.find(t, args[1]->as<Int64>().value); + if (!n2) return t.commit(), false; + auto r = db.graph.edges.insert(t); + r.property("age", args[2]); + r.property("weight", args[3]); + auto &IS = db.graph.edge_type_store.find_or_create("IS"); + r.edge_type(IS); + n2.vlist->update(t)->data.out.add(r.vlist); + n1.vlist->update(t)->data.in.add(r.vlist); + r.from(n2.vlist); + r.to(n1.vlist); + t.commit(); + return true; + }; + queries[15648836733456301916u] = create_edge_v2; + queries[10597108978382323595u] = create_account; queries[5397556489557792025u] = create_labeled_and_named_node; queries[7939106225150551899u] = create_edge; @@ -146,5 +178,6 @@ auto load_queries(Db& db) queries[6813335159006269041u] = update_node; queries[4857652843629217005u] = find_by_label; + return queries; } diff --git a/src/query_engine/traverser/code.hpp b/src/query_engine/traverser/code.hpp index f601b7e88..d7ad495b1 100644 --- a/src/query_engine/traverser/code.hpp +++ b/src/query_engine/traverser/code.hpp @@ -20,15 +20,24 @@ const std::string transaction_begin = "auto& t = db.tx_engine.begin();"; const std::string transaction_commit = "t.commit();"; +const std::string set_property = "{}.property(\"{}\", args[{}]);"; + // create vertex e.g. CREATE (n:PERSON {name: "Test", age: 23}) -const std::string create_vertex = - "auto {} = db.graph.vertices.insert(t);"; -const std::string set_vertex_property = - "{}.property(\"{}\", args[{}]);"; +const std::string create_vertex = "auto {} = db.graph.vertices.insert(t);"; const std::string create_label = "auto &{0} = db.graph.label_store.find_or_create(\"{0}\");"; const std::string add_label = "{}.add_label({});"; +// create edge e.g CREATE (n1)-[r:COST {cost: 100}]->(n2) +const std::string create_edge = "auto {} = db.graph.edges.insert(t);"; +const std::string find_type = + "auto &{0} = db.graph.edge_type_store.find_or_create(\"{0}\");"; +const std::string set_type = "{}.edge_type({});"; +const std::string node_out = "{}.vlist->update(t)->data.out.add({}.vlist);"; +const std::string node_in = "{}.vlist->update(t)->data.in.add({}.vlist);"; +const std::string edge_from = "{}.from({}.vlist);"; +const std::string edge_to = "{}.to({}.vlist);"; + const std::string args_id = "auto id = args[{}]->as<Int32>();"; const std::string vertex_accessor_args_id = @@ -36,21 +45,16 @@ const std::string vertex_accessor_args_id = const std::string match_vertex_by_id = "auto {0} = db.graph.vertices.find(t, args[{1}]->as<Int64>().value);\n" - "if (!{0}) return t.commit(), std::make_shared<QueryResult>();"; + " if (!{0}) return t.commit(), std::make_shared<QueryResult>();"; const std::string match_edge_by_id = "auto {0} = db.graph.edges.find(t, args[{1}]->as<Int64>().value);\n" - "if (!{0}) return t.commit(), std::make_shared<QueryResult>();"; - -const std::string create_edge = - "auto {} = db.graph.edges.insert(t);"; + " if (!{0}) return t.commit(), std::make_shared<QueryResult>();"; const std::string return_empty_result = "return std::make_shared<QueryResult>();"; -const std::string update_property = - "{}.property(\"{}\", args[{}]);"; - +const std::string update_property = "{}.property(\"{}\", args[{}]);"; const std::string todo = "// TODO: {}"; diff --git a/src/query_engine/traverser/cpp_traverser.hpp b/src/query_engine/traverser/cpp_traverser.hpp index 8eae1235f..7c14bb278 100644 --- a/src/query_engine/traverser/cpp_traverser.hpp +++ b/src/query_engine/traverser/cpp_traverser.hpp @@ -3,6 +3,7 @@ #include <string> #include "cypher/visitor/traverser.hpp" + #include "query_engine/code_generator/cpp_generator.hpp" #include "query_engine/code_generator/entity_search.hpp" #include "query_engine/code_generator/structures.hpp" @@ -209,10 +210,9 @@ public: { // TODO: Is that traversal order OK for all cases? Probably NOT. if (ast_pattern.has_next()) { - Traverser::visit(*(ast_pattern.next)); - if (ast_pattern.has_node()) Traverser::visit(*(ast_pattern.node)); - if (ast_pattern.has_relationship()) - Traverser::visit(*(ast_pattern.relationship)); + visit(*ast_pattern.next); + visit(*ast_pattern.node); + visit(*ast_pattern.relationship); } else { Traverser::visit(ast_pattern); } @@ -252,7 +252,7 @@ public: if (!internal_id_expr.has_id()) return; auto name = internal_id_expr.entity_name(); - // because entity_id will be value index inside the parameters array + // because entity_id value will be index inside the parameters array auto index = internal_id_expr.entity_id(); auto &data = generator.action_data(); @@ -275,19 +275,11 @@ public: void visit(ast::Relationship &ast_relationship) override { auto &cypher_data = generator.cypher_data(); + auto &action_data = generator.action_data(); - if (ast_relationship.has_name()) entity = ast_relationship.name(); - - // TODO: simplify somehow - if (state == CypherState::Create) { - if (ast_relationship.has_name()) { - auto name = ast_relationship.name(); - if (!cypher_data.exist(name)) { - clause_action = ClauseAction::CreateRelationship; - create_relationship(name); - } - } - } + if (!ast_relationship.has_name()) + return; + entity = ast_relationship.name(); using ast_direction = ast::Relationship::Direction; using generator_direction = RelationshipData::Direction; @@ -298,9 +290,23 @@ public: if (ast_relationship.direction == ast_direction::Right) direction = generator_direction::Right; - Traverser::visit(ast_relationship); - // TODO: add suport for Direction::Both + + // TODO: simplify somehow + if (state == CypherState::Create) { + if (!cypher_data.exist(entity)) { + clause_action = ClauseAction::CreateRelationship; + create_relationship(entity); + } + } + + if (state == CypherState::Match) { + if (!cypher_data.exist(entity)) { + action_data.actions[entity] = ClauseAction::MatchRelationship; + } + } + + Traverser::visit(ast_relationship); } void visit(ast::RelationshipSpecs &ast_relationship_specs) override @@ -317,6 +323,12 @@ public: } } + if (state == CypherState::Create) { + if (ast_relationship_specs.has_identifier()) { + entity = ast_relationship_specs.name(); + } + } + Traverser::visit(ast_relationship_specs); }