Add BFS integration test
Reviewers: buda Reviewed By: buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D688
This commit is contained in:
parent
abedfb29b1
commit
2e56828dc2
tests
qa/tck_engine/tests/memgraph_V1/features
unit
@ -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' |
|
||||
|
@ -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' |
|
@ -36,10 +36,11 @@ namespace query {
|
||||
|
||||
namespace test_common {
|
||||
|
||||
auto ToInt64List(const TypedValue &t) {
|
||||
std::vector<int64_t> list;
|
||||
template <typename T>
|
||||
auto ToList(const TypedValue &t) {
|
||||
std::vector<T> list;
|
||||
for (auto x : t.Value<std::vector<TypedValue>>()) {
|
||||
list.push_back(x.Value<int64_t>());
|
||||
list.push_back(x.Value<T>());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
@ -111,14 +112,14 @@ auto GetPropertyLookup(AstTreeStorage &storage,
|
||||
property);
|
||||
}
|
||||
auto GetPropertyLookup(
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &dba,
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &,
|
||||
const std::string &name,
|
||||
const std::pair<std::string, GraphDbTypes::Property> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name),
|
||||
prop_pair.first, prop_pair.second);
|
||||
}
|
||||
auto GetPropertyLookup(
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &dba,
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &,
|
||||
Expression *expr,
|
||||
const std::pair<std::string, GraphDbTypes::Property> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(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<query::NamedExpression>(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.
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#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<query::TypedValue>{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<int64_t>(
|
||||
stream.GetResults()[0][0].Value<std::vector<query::TypedValue>>());
|
||||
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<ResultStreamFaker> engine;
|
||||
Dbms dbms;
|
||||
ResultStreamFaker stream;
|
||||
std::vector<std::vector<VertexAccessor>> 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<int64_t> matched_ids;
|
||||
|
||||
for (const auto &result : stream.GetResults()) {
|
||||
const auto &edges =
|
||||
query::test_common::ToList<EdgeAccessor>(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<int64_t>(), 0);
|
||||
for (int i = 1; i < static_cast<int>(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<int64_t>();
|
||||
// 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) {
|
||||
|
@ -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<int64_t>(EvaluateFunction("RANGE", {1, 3})),
|
||||
ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {-1, 5, 2})),
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {-1, 5, 2})),
|
||||
ElementsAre(-1, 1, 3, 5));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 10, 3})),
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {2, 10, 3})),
|
||||
ElementsAre(2, 5, 8));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 2, 2})),
|
||||
EXPECT_THAT(ToList<int64_t>(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<int64_t>(EvaluateFunction("RANGE", {3, 0, 5})),
|
||||
ElementsAre());
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {5, 1, -2})),
|
||||
ElementsAre(5, 3, 1));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {6, 1, -2})),
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {6, 1, -2})),
|
||||
ElementsAre(6, 4, 2));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {2, 2, -3})),
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {2, 2, -3})),
|
||||
ElementsAre(2));
|
||||
EXPECT_THAT(ToInt64List(EvaluateFunction("RANGE", {-2, 4, -1})),
|
||||
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {-2, 4, -1})),
|
||||
ElementsAre());
|
||||
}
|
||||
|
||||
|
@ -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<double>(), 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<int64_t>(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<int64_t>(results[0][6]), UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateGroupByValues) {
|
||||
|
Loading…
Reference in New Issue
Block a user