diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature index 00051c45d..502288597 100644 --- a/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/match.feature @@ -484,32 +484,3 @@ Feature: Match Then the result should be: | n.a | m.a | | 1 | 2 | - - Scenario: Test match BFS depth blocked - Given an empty graph - And having executed: - """ - CREATE (n {a:'0'}) -[:r]-> ({a:'1.1'}) -[:r]-> ({a:'2.1'}), (n) -[:r]-> ({a:'1.2'}) - """ - When executing query: - """ - MATCH (n {a:'0'}) -bfs(e, m| true, 1)-> (m) RETURN n.a, m.a - """ - Then the result should be: - | n.a | m.a | - | '0' | '1.1' | - | '0' | '1.2' | - - Scenario: Test match BFS filtered - Given an empty graph - And having executed: - """ - CREATE (n {a:'0'}) -[:r]-> ({a:'1.1'}) -[:r]-> ({a:'2.1'}), (n) -[:r]-> ({a:'1.2'}) - """ - When executing query: - """ - MATCH (n {a:'0'}) -bfs(e, m| m.a = '1.1' OR m.a = '0', 10)-> (m) RETURN n.a, m.a - """ - Then the result should be: - | n.a | m.a | - | '0' | '1.1' | diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph_bfs.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph_bfs.feature new file mode 100644 index 000000000..4b8bff71b --- /dev/null +++ b/tests/qa/tck_engine/tests/memgraph_V1/features/memgraph_bfs.feature @@ -0,0 +1,30 @@ +Feature: Bfs + + Scenario: Test match BFS depth blocked + Given an empty graph + And having executed: + """ + CREATE (n {a:'0'})-[:r]->({a:'1.1'})-[:r]->({a:'2.1'}), (n)-[:r]->({a:'1.2'}) + """ + When executing query: + """ + MATCH (n {a:'0'})-bfs(e, m| true, 1)->(m) RETURN n.a, m.a + """ + Then the result should be: + | n.a | m.a | + | '0' | '1.1' | + | '0' | '1.2' | + + Scenario: Test match BFS filtered + Given an empty graph + And having executed: + """ + CREATE (n {a:'0'})-[:r]->({a:'1.1'})-[:r]->({a:'2.1'}), (n)-[:r]->({a:'1.2'}) + """ + When executing query: + """ + MATCH (n {a:'0'})-bfs(e, m| m.a = '1.1' OR m.a = '0', 10)->(m) RETURN n.a, m.a + """ + Then the result should be: + | n.a | m.a | + | '0' | '1.1' | diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 2152ad6b7..de8752ec8 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -36,10 +36,11 @@ namespace query { namespace test_common { -auto ToInt64List(const TypedValue &t) { - std::vector list; +template +auto ToList(const TypedValue &t) { + std::vector list; for (auto x : t.Value>()) { - list.push_back(x.Value()); + list.push_back(x.Value()); } return list; }; @@ -111,14 +112,14 @@ auto GetPropertyLookup(AstTreeStorage &storage, property); } auto GetPropertyLookup( - AstTreeStorage &storage, std::unique_ptr &dba, + AstTreeStorage &storage, std::unique_ptr &, const std::string &name, const std::pair &prop_pair) { return storage.Create(storage.Create(name), prop_pair.first, prop_pair.second); } auto GetPropertyLookup( - AstTreeStorage &storage, std::unique_ptr &dba, + AstTreeStorage &storage, std::unique_ptr &, Expression *expr, const std::pair &prop_pair) { return storage.Create(expr, prop_pair.first, @@ -218,7 +219,7 @@ auto GetQuery(AstTreeStorage &storage, Clause *clause, T *... clauses) { } // Helper functions for constructing RETURN and WITH clauses. -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, +void FillReturnBody(AstTreeStorage &, ReturnBody &body, NamedExpression *named_expr) { body.named_expressions.emplace_back(named_expr); } @@ -228,26 +229,26 @@ void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, auto *named_expr = storage.Create(name, ident); body.named_expressions.emplace_back(named_expr); } -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, Limit limit) { +void FillReturnBody(AstTreeStorage &, ReturnBody &body, Limit limit) { body.limit = limit.expression; } -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, Skip skip, +void FillReturnBody(AstTreeStorage &, ReturnBody &body, Skip skip, Limit limit = Limit{}) { body.skip = skip.expression; body.limit = limit.expression; } -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, OrderBy order_by, +void FillReturnBody(AstTreeStorage &, ReturnBody &body, OrderBy order_by, Limit limit = Limit{}) { body.order_by = order_by.expressions; body.limit = limit.expression; } -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, OrderBy order_by, +void FillReturnBody(AstTreeStorage &, ReturnBody &body, OrderBy order_by, Skip skip, Limit limit = Limit{}) { body.order_by = order_by.expressions; body.skip = skip.expression; body.limit = limit.expression; } -void FillReturnBody(AstTreeStorage &storage, ReturnBody &body, Expression *expr, +void FillReturnBody(AstTreeStorage &, ReturnBody &body, Expression *expr, NamedExpression *named_expr) { // This overload supports `RETURN(expr, AS(name))` construct, since // NamedExpression does not inherit Expression. diff --git a/tests/unit/query_engine.cpp b/tests/unit/query_engine.cpp index 6f1f4f017..fc93a4ac5 100644 --- a/tests/unit/query_engine.cpp +++ b/tests/unit/query_engine.cpp @@ -1,3 +1,5 @@ +#include + #include "communication/result_stream_faker.hpp" #include "database/dbms.hpp" #include "database/graph_db_accessor.hpp" @@ -130,7 +132,7 @@ TEST(QueryEngine, Parameters) { {{"2", std::vector{5, 2, 3}}}); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); - auto result = query::test_common::ToInt64List( + auto result = query::test_common::ToList( stream.GetResults()[0][0].Value>()); ASSERT_THAT(result, testing::ElementsAre(5, 2, 3)); } @@ -143,6 +145,119 @@ TEST(QueryEngine, Parameters) { query::UnprovidedParameterError); } } + +// Test bfs end to end. +TEST(QueryEngine, Bfs) { + srand(0); + const auto kNumLevels = 10; + const auto kNumNodesPerLevel = 100; + const auto kNumEdgesPerNode = 100; + const auto kNumUnreachableNodes = 1000; + const auto kNumUnreachableEdges = 100000; + const auto kReachable = "reachable"; + const auto kId = "id"; + + QueryEngine engine; + Dbms dbms; + ResultStreamFaker stream; + std::vector> levels(kNumLevels); + int id = 0; + + // Set up. + { + auto dba = dbms.active(); + auto add_node = [&](int level, bool reachable) { + auto node = dba->InsertVertex(); + node.PropsSet(dba->Property(kId), id++); + node.PropsSet(dba->Property(kReachable), reachable); + levels[level].push_back(node); + return node; + }; + + auto add_edge = [&](VertexAccessor &v1, VertexAccessor &v2, + bool reachable) { + auto edge = dba->InsertEdge(v1, v2, dba->EdgeType("edge")); + edge.PropsSet(dba->Property(kReachable), reachable); + }; + + // Add source node. + add_node(0, true); + + // Add reachable nodes. + for (int i = 1; i < kNumLevels; ++i) { + for (int j = 0; j < kNumNodesPerLevel; ++j) { + auto node = add_node(i, true); + for (int k = 0; k < kNumEdgesPerNode; ++k) { + auto &node2 = levels[i - 1][rand() % levels[i - 1].size()]; + add_edge(node2, node, true); + } + } + } + + // Add unreachable nodes. + for (int i = 0; i < kNumUnreachableNodes; ++i) { + auto node = add_node(rand() % kNumLevels, // Not really important. + false); + for (int j = 0; j < kNumEdgesPerNode; ++j) { + auto &level = levels[rand() % kNumLevels]; + auto &node2 = level[rand() % level.size()]; + add_edge(node2, node, true); + add_edge(node, node2, true); + } + } + + // Add unreachable edges. + for (int i = 0; i < kNumUnreachableEdges; ++i) { + auto &level1 = levels[rand() % kNumLevels]; + auto &node1 = level1[rand() % level1.size()]; + auto &level2 = levels[rand() % kNumLevels]; + auto &node2 = level2[rand() % level2.size()]; + add_edge(node1, node2, false); + } + + dba->Commit(); + } + + auto dba = dbms.active(); + + engine.Run( + "MATCH (n {id: 0})-bfs[r](e, n | n.reachable and e.reachable, 5)->(m) " + "RETURN r", + *dba, stream, {}); + + ASSERT_EQ(stream.GetHeader().size(), 1U); + EXPECT_EQ(stream.GetHeader()[0], "r"); + ASSERT_EQ(stream.GetResults().size(), 5 * kNumNodesPerLevel); + + int expected_level = 1; + int remaining_nodes_in_level = kNumNodesPerLevel; + std::unordered_set matched_ids; + + for (const auto &result : stream.GetResults()) { + const auto &edges = + query::test_common::ToList(result[0].ValueList()); + // Check that path is of expected length. Returned paths should be from + // shorter to longer ones. + EXPECT_EQ(edges.size(), expected_level); + // Check that starting node is correct. + EXPECT_EQ(edges[0].from().PropsAt(dba->Property(kId)).Value(), 0); + for (int i = 1; i < static_cast(edges.size()); ++i) { + // Check that edges form a connected path. + EXPECT_EQ(edges[i - 1].to(), edges[i].from()); + } + auto matched_id = + edges.back().to().PropsAt(dba->Property(kId)).Value(); + // Check that we didn't match that node already. + EXPECT_TRUE(matched_ids.insert(matched_id).second); + // Check that shortest path was found. + EXPECT_TRUE(matched_id > kNumNodesPerLevel * (expected_level - 1) && + matched_id <= kNumNodesPerLevel * expected_level); + if (!--remaining_nodes_in_level) { + remaining_nodes_in_level = kNumNodesPerLevel; + ++expected_level; + } + } +} } int main(int argc, char **argv) { diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index c9dbbf83a..6d4e5cb7c 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -21,7 +21,7 @@ using namespace query; using testing::Pair; using testing::UnorderedElementsAre; using testing::ElementsAre; -using query::test_common::ToInt64List; +using query::test_common::ToList; namespace { @@ -898,22 +898,23 @@ TEST(ExpressionEvaluator, FunctionRange) { EXPECT_THROW(EvaluateFunction("RANGE", {1, TypedValue::Null, 1.3}), QueryRuntimeException); EXPECT_THROW(EvaluateFunction("RANGE", {1, 2, 0}), QueryRuntimeException); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {1, 3})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {1, 3})), ElementsAre(1, 2, 3)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {-1, 5, 2})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {-1, 5, 2})), ElementsAre(-1, 1, 3, 5)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 10, 3})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {2, 10, 3})), ElementsAre(2, 5, 8)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 2, 2})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {2, 2, 2})), ElementsAre(2)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {3, 0, 5})), ElementsAre()); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {5, 1, -2})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {3, 0, 5})), + ElementsAre()); + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {5, 1, -2})), ElementsAre(5, 3, 1)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {6, 1, -2})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {6, 1, -2})), ElementsAre(6, 4, 2)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 2, -3})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {2, 2, -3})), ElementsAre(2)); - EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {-2, 4, -1})), + EXPECT_THAT(ToList(EvaluateFunction("RANGE", {-2, 4, -1})), ElementsAre()); } diff --git a/tests/unit/query_plan_accumulate_aggregate.cpp b/tests/unit/query_plan_accumulate_aggregate.cpp index 73dd1ca1c..e1d8f5d96 100644 --- a/tests/unit/query_plan_accumulate_aggregate.cpp +++ b/tests/unit/query_plan_accumulate_aggregate.cpp @@ -22,7 +22,7 @@ using namespace query; using namespace query::plan; using testing::UnorderedElementsAre; -using query::test_common::ToInt64List; +using query::test_common::ToList; TEST(QueryPlan, Accumulate) { // simulate the following two query execution on an empty db @@ -217,7 +217,7 @@ TEST_F(QueryPlanAggregateOps, WithData) { EXPECT_FLOAT_EQ(results[0][5].Value(), 24 / 3.0); // collect ASSERT_EQ(results[0][6].type(), TypedValue::Type::List); - EXPECT_THAT(ToInt64List(results[0][6]), UnorderedElementsAre(5, 7, 12)); + EXPECT_THAT(ToList(results[0][6]), UnorderedElementsAre(5, 7, 12)); } TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) { @@ -267,7 +267,7 @@ TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) { EXPECT_TRUE(results[0][5].IsNull()); // collect ASSERT_EQ(results[0][6].type(), TypedValue::Type::List); - EXPECT_THAT(ToInt64List(results[0][6]), UnorderedElementsAre()); + EXPECT_THAT(ToList(results[0][6]), UnorderedElementsAre()); } TEST(QueryPlan, AggregateGroupByValues) {