memgraph/include/query_engine/traverser/cpp_traverser.hpp
2016-08-30 20:52:46 +01:00

523 lines
14 KiB
C++

#pragma once
#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"
#include "query_engine/exceptions/exceptions.hpp"
#include "query_engine/traverser/code.hpp"
#include "logging/default.hpp"
struct SetElementState
{
std::string set_entity;
std::string set_prop;
int64_t set_index;
SetElementState() { clear(); }
bool is_defined() const
{
if (set_entity.empty()) return false;
if (set_prop.empty()) return false;
if (set_index < 0) return false;
return true;
}
void clear()
{
set_entity = "";
set_prop = "";
set_index = -1;
}
};
struct PropertyState
{
std::string property_name;
int64_t property_index;
PropertyState() { clear(); }
bool is_defined() const
{
if (property_name.empty()) return false;
if (property_index < 0) return false;
return true;
}
void clear()
{
property_name = "";
property_index = -1;
}
};
// TODO: another idea is to user ast history in order to choose appropriate
// action
class CppTraverser : public Traverser, public Code
{
private:
// currently active entity name (node or relationship name)
std::string entity;
// currently active state
CypherState state; // TODO: move to generator
QueryAction query_action;
ClauseAction clause_action;
// linearization
CppGenerator generator;
Vector<std::string> visited_nodes;
RelationshipData::Direction direction;
SetElementState set_element_state;
PropertyState property_state;
void clear_state()
{
set_element_state.clear();
property_state.clear();
}
// for the first release every query has to have a RETURN clause
// problem is where to put t.commit()
// TODO: remove this constraint
bool has_return;
void finish_query_execution()
{
generator.add_action(QueryAction::TransactionCommit);
code += generator.generate();
generator.clear();
}
Logger logger;
public:
CppTraverser() : logger(logging::log->logger("CppTraverser")) {}
void semantic_check() const
{
if (!has_return)
throw SemanticError("The query doesn't have RETURN clause. Next "
"releases will support query without RETURN "
"clause");
}
void visit(ast::WriteQuery &write_query) override
{
// TODO: crate top level node (TransactionBegin can be
// only at the one place)
generator.add_action(QueryAction::TransactionBegin);
Traverser::visit(write_query);
// TODO: put this inside the top level mentioned above
finish_query_execution();
}
void visit(ast::ReadQuery &read_query) override
{
generator.add_action(QueryAction::TransactionBegin);
Traverser::visit(read_query);
finish_query_execution();
}
void visit(ast::UpdateQuery &update_query) override
{
generator.add_action(QueryAction::TransactionBegin);
Traverser::visit(update_query);
finish_query_execution();
}
void visit(ast::DeleteQuery &delete_query) override
{
generator.add_action(QueryAction::TransactionBegin);
Traverser::visit(delete_query);
finish_query_execution();
}
void visit(ast::ReadWriteQuery &read_write_query) override
{
generator.add_action(QueryAction::TransactionBegin);
Traverser::visit(read_write_query);
finish_query_execution();
}
void visit(ast::Match &ast_match) override
{
state = CypherState::Match;
generator.add_action(QueryAction::Match);
Traverser::visit(ast_match);
}
void visit(ast::Where &ast_where) override
{
state = CypherState::Where;
Traverser::visit(ast_where);
}
void visit(ast::Create &ast_create) override
{
code += generator.generate();
generator.add_action(QueryAction::Create);
state = CypherState::Create;
query_action = QueryAction::Create;
Traverser::visit(ast_create);
}
void visit(ast::Set &ast_set) override
{
code += generator.generate();
generator.add_action(QueryAction::Set);
state = CypherState::Set;
query_action = QueryAction::Set;
Traverser::visit(ast_set);
}
void visit(ast::Return &ast_return) override
{
has_return = true;
generator.add_action(QueryAction::Return);
state = CypherState::Return;
query_action = QueryAction::Return;
Traverser::visit(ast_return);
}
void visit(ast::ReturnList &ast_return_list) override
{
Traverser::visit(ast_return_list);
}
void visit(ast::PatternList &ast_pattern_list) override
{
Traverser::visit(ast_pattern_list);
}
void visit(ast::Pattern &ast_pattern) override
{
// TODO: Is that traversal order OK for all cases? Probably NOT.
if (ast_pattern.has_next()) {
visit(*ast_pattern.next);
visit(*ast_pattern.node);
visit(*ast_pattern.relationship);
} else {
Traverser::visit(ast_pattern);
}
}
void visit(ast::Node &ast_node) override
{
auto &action_data = generator.action_data();
auto &cypher_data = generator.cypher_data();
if (!ast_node.has_identifier()) return;
auto name = ast_node.idn->name;
entity = name;
visited_nodes.push_back(name);
if (state == CypherState::Match) {
action_data.actions[name] = ClauseAction::MatchNode;
}
if (state == CypherState::Create) {
if (cypher_data.status(name) == EntityStatus::Matched) {
action_data.actions[name] = ClauseAction::MatchNode;
} else {
action_data.actions[name] = ClauseAction::CreateNode;
}
}
Traverser::visit(ast_node);
// this is here because of RETURN clause
// CREATE (n {...}) RETURN n
if (cypher_data.status(name) != EntityStatus::Matched &&
state == CypherState::Create)
{
cypher_data.node_created(name);
}
}
void visit(ast::And &ast_and) override { Traverser::visit(ast_and); }
void visit(ast::InternalIdExpr &internal_id_expr) override
{
if (!internal_id_expr.has_entity()) return;
if (!internal_id_expr.has_id()) return;
auto name = internal_id_expr.entity_name();
// because entity_id value will be index inside the parameters array
auto index = internal_id_expr.entity_id();
auto &data = generator.action_data();
data.parameter_index[ParameterIndexKey(InternalId, name)] = index;
data.csm.search_cost(name, entity_search::search_internal_id,
entity_search::internal_id_cost);
}
// -- RELATIONSHIP --
void create_relationship(const std::string &name)
{
auto &data = generator.action_data();
data.actions[name] = ClauseAction::CreateRelationship;
auto nodes = visited_nodes.last_two();
data.relationship_data.emplace(name,
RelationshipData(nodes, direction));
}
void visit(ast::Relationship &ast_relationship) override
{
auto &cypher_data = generator.cypher_data();
auto &action_data = generator.action_data();
if (!ast_relationship.has_name())
return;
entity = ast_relationship.name();
using ast_direction = ast::Relationship::Direction;
using generator_direction = RelationshipData::Direction;
if (ast_relationship.direction == ast_direction::Left)
direction = generator_direction::Left;
if (ast_relationship.direction == ast_direction::Right)
direction = generator_direction::Right;
// 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
{
if (state == CypherState::Match) {
if (ast_relationship_specs.has_identifier()) {
auto name = ast_relationship_specs.name();
auto &cypher_data = generator.cypher_data();
if (!cypher_data.exist(name)) {
clause_action = ClauseAction::MatchRelationship;
auto &data = generator.action_data();
data.actions[name] = ClauseAction::MatchRelationship;
}
}
}
if (state == CypherState::Create) {
if (ast_relationship_specs.has_identifier()) {
entity = ast_relationship_specs.name();
}
}
Traverser::visit(ast_relationship_specs);
}
void visit(ast::RelationshipTypeList &ast_relationship_type_list) override
{
auto &action_data = generator.action_data();
if (ast_relationship_type_list.has_value()) {
auto type = ast_relationship_type_list.value->name;
action_data.add_entity_tag(entity, type);
action_data.csm.search_cost(
entity, entity_search::search_type_index,
entity_search::type_cost);
}
Traverser::visit(ast_relationship_type_list);
}
void visit(ast::LabelList &ast_label_list) override
{
auto &action_data = generator.action_data();
if (!ast_label_list.has_value()) return;
auto label = ast_label_list.value->name;
action_data.add_entity_tag(entity, label);
action_data.csm.search_cost(entity, entity_search::search_label_index,
entity_search::label_cost);
Traverser::visit(ast_label_list);
}
void visit(ast::PropertyList &ast_property_list) override
{
Traverser::visit(ast_property_list);
}
void visit(ast::Property &ast_property) override
{
clear_state();
Traverser::visit(ast_property);
// TODO: too ugly refactor somehow (clear_state part is awful)
if (entity.empty() || !property_state.is_defined()) {
clear_state();
return;
}
auto prop = property_state.property_name;
auto index = property_state.property_index;
auto &data = generator.action_data();
data.parameter_index.emplace(ParameterIndexKey(entity, prop), index);
data.add_entitiy_property(entity, prop);
clear_state();
}
void visit(ast::Identifier &ast_identifier) override
{
property_state.property_name = ast_identifier.name;
if (state == CypherState::Delete) {
auto &action_data = generator.action_data();
auto name = ast_identifier.name;
auto &cypher_data = generator.cypher_data();
if (cypher_data.type(name) == EntityType::Node)
action_data.actions[name] = ClauseAction::DeleteNode;
if (cypher_data.type(name) == EntityType::Relationship)
action_data.actions[name] = ClauseAction::DeleteRelationship;
}
}
void visit(ast::Long &ast_long) override
{
set_element_state.set_index = ast_long.value;
property_state.property_index = ast_long.value;
}
// -- SET subtree (node)
// QUERY: SET n.name = "bla", ...
// STRIP: SET n.name = 0, ...
// STATE: entity: n; prop: name; set_index: 0
void visit(ast::SetList &ast_set_list) override
{
Traverser::visit(ast_set_list);
}
void visit(ast::SetElement &ast_set_element) override
{
Traverser::visit(ast_set_element);
if (!set_element_state.is_defined()) {
clear_state();
return;
}
auto entity = set_element_state.set_entity;
auto prop = set_element_state.set_prop;
auto index = set_element_state.set_index;
auto &cypher_data = generator.cypher_data();
auto entity_type = cypher_data.type(entity);
if (entity_type == EntityType::None)
throw SemanticError("Entity (" + entity + ") doesn't exist");
auto &action_data = generator.action_data();
if (entity_type == EntityType::Node)
action_data.actions[entity] = ClauseAction::UpdateNode;
if (entity_type == EntityType::Relationship)
action_data.actions[entity] = ClauseAction::UpdateRelationship;
action_data.parameter_index.emplace(ParameterIndexKey(entity, prop),
index);
action_data.add_entitiy_property(entity, prop);
clear_state();
}
void visit(ast::Accessor &ast_accessor) override
{
if (!ast_accessor.has_entity()) return;
auto &action_data = generator.action_data();
if (state == CypherState::Return) {
auto &return_elements = action_data.return_elements;
auto &entity = ast_accessor.entity_name();
if (!ast_accessor.has_prop()) {
return_elements.emplace_back(ReturnElement(entity));
} else {
auto &property = ast_accessor.entity_prop();
return_elements.emplace_back(ReturnElement(entity, property));
}
}
if (!ast_accessor.has_prop()) return;
if (state == CypherState::Set) {
set_element_state.set_entity = ast_accessor.entity_name();
set_element_state.set_prop = ast_accessor.entity_prop();
}
}
void visit(ast::SetValue &ast_set_value) override
{
Traverser::visit(ast_set_value);
}
void visit(ast::Delete& ast_delete) override
{
code += generator.generate();
state = CypherState::Delete;
generator.add_action(QueryAction::Delete);
Traverser::visit(ast_delete);
code += generator.generate();
}
};