diff --git a/CMakeLists.txt b/CMakeLists.txt index adc417b88..c88bae3a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,7 @@ set(memgraph_src_files ${src_dir}/query/frontend/ast/cypher_main_visitor.cpp ${src_dir}/query/backend/cpp/typed_value.cpp ${src_dir}/query/frontend/logical/planner.cpp + ${src_dir}/query/frontend/semantic/symbol_generator.cpp ) # ----------------------------------------------------------------------------- diff --git a/src/query/frontend/semantic/symbol_generator.cpp b/src/query/frontend/semantic/symbol_generator.cpp new file mode 100644 index 000000000..fa9e643fa --- /dev/null +++ b/src/query/frontend/semantic/symbol_generator.cpp @@ -0,0 +1,135 @@ +// Copyright 2017 Memgraph +// +// Created by Teon Banek on 24-03-2017 + +#include "query/frontend/semantic/symbol_generator.hpp" + +namespace query { + +auto SymbolGenerator::CreateVariable(const std::string &name, + SymbolGenerator::Variable::Type type) { + auto symbol = symbol_table_.CreateSymbol(name); + auto variable = SymbolGenerator::Variable{symbol, type}; + scope_.variables[name] = variable; + return variable; +} + +auto SymbolGenerator::GetOrCreateVariable( + const std::string &name, SymbolGenerator::Variable::Type type) { + auto search = scope_.variables.find(name); + if (search != scope_.variables.end()) { + auto variable = search->second; + if (type != SymbolGenerator::Variable::Type::Any && type != variable.type) { + throw TypeMismatchError(name, TypeToString(variable.type), + TypeToString(type)); + } + return search->second; + } + return CreateVariable(name, type); +} + +// Clauses + +void SymbolGenerator::Visit(Create &create) { scope_.in_create = true; } +void SymbolGenerator::PostVisit(Create &create) { scope_.in_create = false; } + +void SymbolGenerator::PostVisit(Return &ret) { + for (auto &named_expr : ret.named_expressions_) { + // Named expressions establish bindings for expressions which come after + // return, but not for the expressions contained inside. + symbol_table_[*named_expr] = CreateVariable(named_expr->name_).symbol; + } +} + +// Expressions + +void SymbolGenerator::Visit(Identifier &ident) { + Symbol symbol; + if (scope_.in_pattern) { + // Patterns can bind new symbols or reference already bound. But there + // are the following special cases: + // 1) Expressions in property maps `{prop_name: expr}` can only reference + // bound variables. + // 2) Patterns used to create nodes and edges cannot redeclare already + // established bindings. Declaration only happens in single node + // patterns and in edge patterns. OpenCypher example, + // `MATCH (n) CREATE (n)` should throw an error that `n` is already + // declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed, + // since `n` now references the bound node instead of declaring it. + // Additionally, we will support edge referencing in pattern: + // `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r`, which would + // usually raise redeclaration of `r`. + if (scope_.in_property_map && !HasVariable(ident.name_)) { + // Case 1) + throw UnboundVariableError(ident.name_); + } else if ((scope_.in_create_node || scope_.in_create_edge) && + HasVariable(ident.name_)) { + // Case 2) + throw RedeclareVariableError(ident.name_); + } + auto type = Variable::Type::Vertex; + if (scope_.in_edge_atom) { + type = Variable::Type::Edge; + } + symbol = GetOrCreateVariable(ident.name_, type).symbol; + } else { + // Everything else references a bound symbol. + if (!HasVariable(ident.name_)) throw UnboundVariableError(ident.name_); + symbol = scope_.variables[ident.name_].symbol; + } + symbol_table_[ident] = symbol; +} + +// Pattern and its subparts. + +void SymbolGenerator::Visit(Pattern &pattern) { + scope_.in_pattern = true; + if (scope_.in_create && pattern.atoms_.size() == 1) { + debug_assert(dynamic_cast(pattern.atoms_[0]), + "Expected a single NodeAtom in Pattern"); + scope_.in_create_node = true; + } +} + +void SymbolGenerator::PostVisit(Pattern &pattern) { scope_.in_pattern = false; } + +void SymbolGenerator::Visit(NodeAtom &node_atom) { + scope_.in_node_atom = true; + scope_.in_property_map = true; + for (auto kv : node_atom.properties_) { + kv.second->Accept(*this); + } + scope_.in_property_map = false; +} + +void SymbolGenerator::PostVisit(NodeAtom &node_atom) { + scope_.in_node_atom = false; +} + +void SymbolGenerator::Visit(EdgeAtom &edge_atom) { + scope_.in_edge_atom = true; + if (scope_.in_create) { + scope_.in_create_edge = true; + if (edge_atom.edge_types_.size() != 1) { + throw SemanticException( + "A single relationship type must be specified " + "when creating an edge."); + } + if (edge_atom.direction_ == EdgeAtom::Direction::BOTH) { + throw SemanticException( + "Bidirectional relationship are not supported " + "when creating an edge"); + } + } +} + +void SymbolGenerator::PostVisit(EdgeAtom &edge_atom) { + scope_.in_edge_atom = false; + scope_.in_create_edge = false; +} + +bool SymbolGenerator::HasVariable(const std::string &name) { + return scope_.variables.find(name) != scope_.variables.end(); +} + +} // namespace query diff --git a/src/query/frontend/semantic/symbol_generator.hpp b/src/query/frontend/semantic/symbol_generator.hpp index bf0558e34..b0ae49657 100644 --- a/src/query/frontend/semantic/symbol_generator.hpp +++ b/src/query/frontend/semantic/symbol_generator.hpp @@ -1,3 +1,7 @@ +// Copyright 2017 Memgraph +// +// Created by Teon Banek on 11-03-2017 + #pragma once #include "query/exceptions.hpp" @@ -6,6 +10,12 @@ namespace query { +/// +/// Visits the AST and generates symbols for variables. +/// +/// During the process of symbol generation, simple semantic checks are +/// performed. Such as, redeclaring a variable or conflicting expectations of +/// variable types. class SymbolGenerator : public TreeVisitorBase { public: SymbolGenerator(SymbolTable &symbol_table) : symbol_table_(symbol_table) {} @@ -14,103 +24,20 @@ class SymbolGenerator : public TreeVisitorBase { using TreeVisitorBase::PostVisit; // Clauses - void Visit(Create &create) override { - scope_.in_create = true; - } - void PostVisit(Create &create) override { - scope_.in_create = false; - } - - void PostVisit(Return &ret) override { - for (auto &named_expr : ret.named_expressions_) { - // Named expressions establish bindings for expressions which come after - // return, but not for the expressions contained inside. - symbol_table_[*named_expr] = CreateVariable(named_expr->name_).symbol; - } - } + void Visit(Create &create) override; + void PostVisit(Create &create) override; + void PostVisit(Return &ret) override; // Expressions - void Visit(Identifier &ident) override { - Symbol symbol; - if (scope_.in_pattern) { - // Patterns can bind new symbols or reference already bound. But there - // are the following special cases: - // 1) Expressions in property maps `{prop_name: expr}` can only reference - // bound variables. - // 2) Patterns used to create nodes and edges cannot redeclare already - // established bindings. Declaration only happens in single node - // patterns and in edge patterns. OpenCypher example, - // `MATCH (n) CREATE (n)` should throw an error that `n` is already - // declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed, - // since `n` now references the bound node instead of declaring it. - // Additionally, we will support edge referencing in pattern: - // `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r`, which would - // usually raise redeclaration of `r`. - if (scope_.in_property_map && !HasVariable(ident.name_)) { - // Case 1) - throw UnboundVariableError(ident.name_); - } else if ((scope_.in_create_node || scope_.in_create_edge) && - HasVariable(ident.name_)) { - // Case 2) - throw RedeclareVariableError(ident.name_); - } - auto type = Variable::Type::Vertex; - if (scope_.in_edge_atom) { - type = Variable::Type::Edge; - } - symbol = GetOrCreateVariable(ident.name_, type).symbol; - } else { - // Everything else references a bound symbol. - if (!HasVariable(ident.name_)) - throw UnboundVariableError(ident.name_); - symbol = scope_.variables[ident.name_].symbol; - } - symbol_table_[ident] = symbol; - } + void Visit(Identifier &ident) override; // Pattern and its subparts. - void Visit(Pattern &pattern) override { - scope_.in_pattern = true; - if (scope_.in_create && pattern.atoms_.size() == 1) { - debug_assert(dynamic_cast(pattern.atoms_[0]), - "Expected a single NodeAtom in Pattern"); - scope_.in_create_node = true; - } - } - void PostVisit(Pattern &pattern) override { - scope_.in_pattern = false; - } - - void Visit(NodeAtom &node_atom) override { - scope_.in_node_atom = true; - scope_.in_property_map = true; - for (auto kv : node_atom.properties_) { - kv.second->Accept(*this); - } - scope_.in_property_map = false; - } - void PostVisit(NodeAtom &node_atom) override { - scope_.in_node_atom = false; - } - - void Visit(EdgeAtom &edge_atom) override { - scope_.in_edge_atom = true; - if (scope_.in_create) { - scope_.in_create_edge = true; - if (edge_atom.edge_types_.size() != 1) { - throw SemanticException("A single relationship type must be specified " - "when creating an edge."); - } - if (edge_atom.direction_ == EdgeAtom::Direction::BOTH) { - throw SemanticException("Bidirectional relationship are not supported " - "when creating an edge"); - } - } - } - void PostVisit(EdgeAtom &edge_atom) override { - scope_.in_edge_atom = false; - scope_.in_create_edge = false; - } + void Visit(Pattern &pattern) override; + void PostVisit(Pattern &pattern) override; + void Visit(NodeAtom &node_atom) override; + void PostVisit(NodeAtom &node_atom) override; + void Visit(EdgeAtom &edge_atom) override; + void PostVisit(EdgeAtom &edge_atom) override; private: // A variable stores the associated symbol and its type. @@ -143,38 +70,20 @@ class SymbolGenerator : public TreeVisitorBase { std::map variables; }; - bool HasVariable(const std::string &name) { - return scope_.variables.find(name) != scope_.variables.end(); - } + bool HasVariable(const std::string &name); // Returns a new variable with a freshly generated symbol. Previous mapping // of the same name to a different variable is replaced with the new one. - Variable CreateVariable( - const std::string &name, Variable::Type type = Variable::Type::Any) { - auto symbol = symbol_table_.CreateSymbol(name); - auto variable = Variable{symbol, type}; - scope_.variables[name] = variable; - return variable; - } + auto CreateVariable(const std::string &name, + Variable::Type type = Variable::Type::Any); // Returns the variable by name. If the mapping already exists, checks if the // types match. Otherwise, returns a new variable. - Variable GetOrCreateVariable( - const std::string &name, Variable::Type type = Variable::Type::Any) { - auto search = scope_.variables.find(name); - if (search != scope_.variables.end()) { - auto variable = search->second; - if (type != Variable::Type::Any && type != variable.type) { - throw TypeMismatchError(name, TypeToString(variable.type), - TypeToString(type)); - } - return search->second; - } - return CreateVariable(name, type); - } + auto GetOrCreateVariable(const std::string &name, + Variable::Type type = Variable::Type::Any); SymbolTable &symbol_table_; Scope scope_; }; -} +} // namespace query