Cypher CRUD support is becomes bigger and bigger. Implemented: create relationship, match relationship and update relationship
This commit is contained in:
parent
6970170f69
commit
d04cf101b4
@ -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
|
||||
|
17
src/memgraph_bolt.cpp
Normal file
17
src/memgraph_bolt.cpp
Normal file
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ struct ParameterIndexKey
|
||||
}
|
||||
};
|
||||
|
||||
struct RelationshipData : public EntityData
|
||||
struct RelationshipData
|
||||
{
|
||||
enum class Direction
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
@ -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) {}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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: {}";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user