#include #include #include #include #include #include "antlr4-runtime.h" #include "gtest/gtest.h" #include "gmock/gmock.h" #include "query/backend/cpp/cypher_main_visitor.cpp" #include "query/frontend/opencypher/parser.hpp" using namespace ::testing; namespace { using namespace backend::cpp; class ParserTables { template auto FilterAnies(std::unordered_map map) { std::unordered_map filtered; for (auto x : map) { if (x.second.is()) { filtered[x.first] = x.second.as(); } } return filtered; } public: ParserTables(const std::string &query) { frontend::opencypher::Parser parser(query); auto *tree = parser.tree(); CypherMainVisitor visitor; visitor.visit(tree); identifiers_map_ = visitor.ids_map().back(); symbol_table_ = visitor.symbol_table(); pattern_parts_ = FilterAnies(symbol_table_); nodes_ = FilterAnies(symbol_table_); relationships_ = FilterAnies(symbol_table_); } std::unordered_map identifiers_map_; std::unordered_map symbol_table_; std::unordered_map pattern_parts_; std::unordered_map nodes_; std::unordered_map relationships_; }; // TODO: Once expression evaluation is implemented, we should also test if // property values are equal. void CompareNodes(std::pair node_entry, std::vector labels, std::vector property_keys) { auto node = node_entry.second; ASSERT_EQ(node_entry.first, node.output_id); ASSERT_THAT(node.labels, UnorderedElementsAreArray(labels.begin(), labels.end())); std::vector node_property_keys; for (auto x : node.properties) { node_property_keys.push_back(x.first); } ASSERT_THAT( node_property_keys, UnorderedElementsAreArray(property_keys.begin(), property_keys.end())); } // If has_range is false, lower and upper bound values are ignored. // TODO: Once expression evaluation is implemented, we should also test if // property values are equal. void CompareRelationships( std::pair relationship_entry, Relationship::Direction direction, std::vector types, std::vector property_keys, bool has_range, int64_t lower_bound = 1LL, int64_t upper_bound = LLONG_MAX) { auto relationship = relationship_entry.second; ASSERT_EQ(relationship_entry.first, relationship.output_id); ASSERT_EQ(relationship.direction, direction); ASSERT_THAT(relationship.types, UnorderedElementsAreArray(types.begin(), types.end())); std::vector relationship_property_keys; for (auto x : relationship.properties) { relationship_property_keys.push_back(x.first); } ASSERT_THAT( relationship_property_keys, UnorderedElementsAreArray(property_keys.begin(), property_keys.end())); ASSERT_EQ(relationship.has_range, has_range); if (!has_range) return; ASSERT_EQ(relationship.lower_bound, lower_bound); ASSERT_EQ(relationship.upper_bound, upper_bound); } // SyntaxException on incorrect syntax. TEST(CompilerStructuresTest, SyntaxException) { ASSERT_THROW(ParserTables("CREATE ()-[*1...2]-()"), frontend::opencypher::SyntaxException); } // Empty node. TEST(CompilerStructuresTest, NodePatternEmpty) { ParserTables parser("CREATE ()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.nodes_.size(), 1U); CompareNodes(*parser.nodes_.begin(), {}, {}); } // Node with variable. TEST(CompilerStructuresTest, NodePatternVariable) { ParserTables parser("CREATE (var)"); ASSERT_EQ(parser.identifiers_map_.size(), 1U); ASSERT_NE(parser.identifiers_map_.find("var"), parser.identifiers_map_.end()); ASSERT_EQ(parser.nodes_.size(), 1U); auto output_identifier = parser.identifiers_map_["var"]; ASSERT_NE(parser.nodes_.find(output_identifier), parser.nodes_.end()); CompareNodes(*parser.nodes_.begin(), {}, {}); } // Node with labels. TEST(CompilerStructuresTest, NodePatternLabels) { ParserTables parser("CREATE (:label1:label2:label3)"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.nodes_.size(), 1U); CompareNodes(*parser.nodes_.begin(), {"label1", "label2", "label3"}, {}); } // Node with properties. TEST(CompilerStructuresTest, NodePatternProperties) { ParserTables parser("CREATE ({age: 5, name: \"John\", surname: \"Smith\"})"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.nodes_.size(), 1U); CompareNodes(*parser.nodes_.begin(), {}, {"age", "name", "surname"}); } // Relationship without relationship details. TEST(CompilerStructuresTest, RelationshipPatternNoDetails) { ParserTables parser("CREATE ()--()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, false); } // Relationship with empty relationship details. TEST(CompilerStructuresTest, RelationshipPatternEmptyDetails) { ParserTables parser("CREATE ()-[]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, false); } // Relationship with left direction. TEST(CompilerStructuresTest, RelationshipPatternLeftDirection) { ParserTables parser("CREATE ()<--()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::LEFT, {}, {}, false); } // Relationship with right direction. TEST(CompilerStructuresTest, RelationshipPatternRightDirection) { ParserTables parser("CREATE ()-[]->()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::RIGHT, {}, {}, false); } // Relationship with both directions. TEST(CompilerStructuresTest, RelationshipPatternBothDirection) { ParserTables parser("CREATE ()<-[]->()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, false); } // Relationship with unbounded variable range. TEST(CompilerStructuresTest, RelationshipPatternUnbounded) { ParserTables parser("CREATE ()-[*]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, true, 1, LLONG_MAX); } // Relationship with lower bounded variable range. TEST(CompilerStructuresTest, RelationshipPatternLowerBounded) { ParserTables parser("CREATE ()-[*5..]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, true, 5, LLONG_MAX); } // Relationship with upper bounded variable range. TEST(CompilerStructuresTest, RelationshipPatternUpperBounded) { ParserTables parser("CREATE ()-[*..10]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, true, 1, 10); } // Relationship with lower and upper bounded variable range. TEST(CompilerStructuresTest, RelationshipPatternLowerUpperBounded) { ParserTables parser("CREATE ()-[*5..10]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, true, 5, 10); } // Relationship with fixed number of edges. TEST(CompilerStructuresTest, RelationshipPatternFixedRange) { ParserTables parser("CREATE ()-[*10]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, true, 10, 10); } // Relationship with invalid bound (larger than long long). TEST(CompilerStructuresTest, RelationshipPatternInvalidBound) { ASSERT_THROW( ParserTables parser("CREATE ()-[*100000000000000000000000000]-()"), SemanticException); } // Relationship with variable TEST(CompilerStructuresTest, RelationshipPatternVariable) { ParserTables parser("CREATE ()-[var]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 1U); ASSERT_NE(parser.identifiers_map_.find("var"), parser.identifiers_map_.end()); ASSERT_EQ(parser.relationships_.size(), 1U); auto output_identifier = parser.identifiers_map_["var"]; ASSERT_NE(parser.relationships_.find(output_identifier), parser.relationships_.end()); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {}, false); } // Relationship with labels. TEST(CompilerStructuresTest, RelationshipPatternLabels) { ParserTables parser("CREATE ()-[:label1|label2|:label3]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {"label1", "label2", "label3"}, {}, false); } // Relationship with properties. TEST(CompilerStructuresTest, RelationshipPatternProperties) { ParserTables parser( "CREATE ()-[{age: 5, name: \"John\", surname: \"Smith\"}]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.relationships_.size(), 1U); CompareRelationships(*parser.relationships_.begin(), Relationship::Direction::BOTH, {}, {"age", "name", "surname"}, false); } // PatternPart. TEST(CompilerStructuresTest, PatternPart) { ParserTables parser("CREATE ()--()"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.pattern_parts_.size(), 1U); ASSERT_EQ(parser.relationships_.size(), 1U); ASSERT_EQ(parser.nodes_.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.nodes.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.relationships.size(), 1U); } // PatternPart in braces. TEST(CompilerStructuresTest, PatternPartBraces) { ParserTables parser("CREATE ((()--()))"); ASSERT_EQ(parser.identifiers_map_.size(), 0U); ASSERT_EQ(parser.pattern_parts_.size(), 1U); ASSERT_EQ(parser.relationships_.size(), 1U); ASSERT_EQ(parser.nodes_.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.nodes.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.relationships.size(), 1U); } // PatternPart with variable. TEST(CompilerStructuresTest, PatternPartVariable) { ParserTables parser("CREATE var=()--()"); ASSERT_EQ(parser.identifiers_map_.size(), 1U); ASSERT_EQ(parser.pattern_parts_.size(), 1U); ASSERT_EQ(parser.relationships_.size(), 1U); ASSERT_EQ(parser.nodes_.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.nodes.size(), 2U); ASSERT_EQ(parser.pattern_parts_.begin()->second.relationships.size(), 1U); ASSERT_NE(parser.identifiers_map_.find("var"), parser.identifiers_map_.end()); auto output_identifier = parser.identifiers_map_["var"]; ASSERT_NE(parser.pattern_parts_.find(output_identifier), parser.pattern_parts_.end()); } // Multiple nodes with same variable and properties. TEST(CompilerStructuresTest, MultipleNodesWithVariableAndProperties) { ASSERT_THROW(ParserTables parser("CREATE (a {b: 5})-[]-(a {c: 5})"), SemanticException); } // Multiple nodes with same variable name. TEST(CompilerStructuresTest, MultipleNodesWithVariable) { ParserTables parser("CREATE (a {b: 5, c: 5})-[]-(a)"); ASSERT_EQ(parser.identifiers_map_.size(), 1U); ASSERT_EQ(parser.pattern_parts_.size(), 1U); ASSERT_EQ(parser.relationships_.size(), 1U); ASSERT_EQ(parser.nodes_.size(), 1U); auto pattern_part = parser.pattern_parts_.begin()->second; ASSERT_EQ(pattern_part.nodes.size(), 2U); ASSERT_EQ(pattern_part.relationships.size(), 1U); ASSERT_EQ(pattern_part.nodes[0], pattern_part.nodes[1]); } // Multiple relationships with same variable name and properties. TEST(CompilerStructuresTest, MultipleRelationshipsWithVariableAndProperties) { ASSERT_THROW(ParserTables parser("CREATE ()-[e {a: 5}]-()-[e {c: 5}]-()"), SemanticException); } // Multiple relationships with same variable name. TEST(CompilerStructuresTest, MultipleRelationshipsWithVariable) { ParserTables parser("CREATE ()-[a {a: 5}]-()-[a]-()"); ASSERT_EQ(parser.identifiers_map_.size(), 1U); ASSERT_EQ(parser.pattern_parts_.size(), 1U); ASSERT_EQ(parser.relationships_.size(), 1U); ASSERT_EQ(parser.nodes_.size(), 3U); auto pattern_part = parser.pattern_parts_.begin()->second; ASSERT_EQ(pattern_part.nodes.size(), 3U); ASSERT_EQ(pattern_part.relationships.size(), 2U); ASSERT_NE(pattern_part.nodes[0], pattern_part.nodes[1]); ASSERT_NE(pattern_part.nodes[1], pattern_part.nodes[2]); ASSERT_NE(pattern_part.nodes[0], pattern_part.nodes[2]); ASSERT_EQ(pattern_part.relationships[0], pattern_part.relationships[1]); } // Different structures (nodes, realtionships, patterns) with same variable // name. TEST(CompilerStructuresTest, DifferentTypesWithVariable) { ASSERT_THROW(ParserTables parser("CREATE a=(a)"), SemanticException); ASSERT_THROW(ParserTables parser("CREATE (a)-[a]-()"), SemanticException); ASSERT_THROW(ParserTables parser("CREATE a=()-[a]-()"), SemanticException); } } int main(int argc, char **argv) { InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }