2017-08-21 21:44:35 +08:00
|
|
|
#include <cstdlib>
|
|
|
|
|
2017-06-15 00:53:02 +08:00
|
|
|
#include "communication/result_stream_faker.hpp"
|
2017-07-20 00:14:59 +08:00
|
|
|
#include "database/graph_db_accessor.hpp"
|
2017-06-15 00:53:02 +08:00
|
|
|
#include "gmock/gmock.h"
|
|
|
|
#include "gtest/gtest.h"
|
2017-07-20 00:14:59 +08:00
|
|
|
#include "query/exceptions.hpp"
|
Flags cleanup and QueryEngine removal
Summary:
I started with cleaning flags up (removing unused ones, documenting undocumented ones). There were some flags to remove in `QueryEngine`. Seeing how we never use hardcoded queries (AFAIK last Mislav's testing also indicated they aren't faster then interpretation), when removing those unused flags the `QueryEngine` becomes obsolete. That means that a bunch of other stuff becomes obsolete, along with the hardcoded queries. So I removed it all (this has been discussed and approved on the daily).
Some flags that were previously undocumented in `docs/user_technical/installation` are now documented. The following flags are NOT documented and in my opinion should not be displayed when starting `./memgraph --help` (@mferencevic):
```
query_vertex_count_to_expand_existsing (from rule_based_planner.cpp)
query_max_plans (rule_based_planner.cpp)
```
If you think that another organization is needed w.r.t. flag visibility, comment.
@teon.banek: I had to remove some stuff from CMakeLists to make it buildable. Please review what I removed and clean up if necessary if/when this lands. If the needed changes are minor, you can also comment.
Reviewers: buda, mislav.bradac, teon.banek, mferencevic
Reviewed By: buda, mislav.bradac
Subscribers: pullbot, mferencevic, teon.banek
Differential Revision: https://phabricator.memgraph.io/D825
2017-09-22 22:17:09 +08:00
|
|
|
#include "query/interpreter.hpp"
|
2017-07-20 00:14:59 +08:00
|
|
|
#include "query/typed_value.hpp"
|
|
|
|
#include "query_common.hpp"
|
2017-06-15 00:53:02 +08:00
|
|
|
|
|
|
|
// TODO: This is not a unit test, but tests/integration dir is chaotic at the
|
|
|
|
// moment. After tests refactoring is done, move/rename this.
|
|
|
|
|
2017-12-22 20:39:31 +08:00
|
|
|
class InterpreterTest : public ::testing::Test {
|
|
|
|
protected:
|
2018-01-12 22:17:04 +08:00
|
|
|
database::SingleNode db_;
|
2018-03-13 17:35:14 +08:00
|
|
|
query::Interpreter interpreter_{db_};
|
2017-12-22 20:39:31 +08:00
|
|
|
|
|
|
|
ResultStreamFaker Interpret(
|
|
|
|
const std::string &query,
|
|
|
|
const std::map<std::string, query::TypedValue> params = {}) {
|
2018-01-12 22:17:04 +08:00
|
|
|
database::GraphDbAccessor dba(db_);
|
2017-12-22 20:39:31 +08:00
|
|
|
ResultStreamFaker result;
|
|
|
|
interpreter_(query, dba, params, false).PullAll(result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
2017-06-15 00:53:02 +08:00
|
|
|
|
|
|
|
// Run query with different ast twice to see if query executes correctly when
|
|
|
|
// ast is read from cache.
|
2017-12-22 20:39:31 +08:00
|
|
|
TEST_F(InterpreterTest, AstCache) {
|
2017-06-15 00:53:02 +08:00
|
|
|
{
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 2 + 3");
|
2017-06-26 21:42:13 +08:00
|
|
|
ASSERT_EQ(stream.GetHeader().size(), 1U);
|
|
|
|
EXPECT_EQ(stream.GetHeader()[0], "2 + 3");
|
2017-06-15 00:53:02 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 5);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, different literals.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 5 + 4");
|
2017-06-15 00:53:02 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 9);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Different ast (because of different types).
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 5.5 + 4");
|
2017-06-15 00:53:02 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 9.5);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, same literals.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 2 + 3");
|
2017-06-15 00:53:02 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 5);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, different literals.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 10.5 + 1");
|
2017-06-15 00:53:02 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 11.5);
|
|
|
|
}
|
2017-06-16 19:40:42 +08:00
|
|
|
{
|
|
|
|
// Cached ast, same literals, different whitespaces.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 10.5 + 1");
|
2017-06-26 21:42:13 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 11.5);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, same literals, different named header.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN 10.5+1");
|
2017-06-26 21:42:13 +08:00
|
|
|
ASSERT_EQ(stream.GetHeader().size(), 1U);
|
|
|
|
EXPECT_EQ(stream.GetHeader()[0], "10.5+1");
|
2017-06-16 19:40:42 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 11.5);
|
|
|
|
}
|
2017-06-15 00:53:02 +08:00
|
|
|
}
|
2017-07-20 00:14:59 +08:00
|
|
|
|
|
|
|
// Run query with same ast multiple times with different parameters.
|
2017-12-22 20:39:31 +08:00
|
|
|
TEST_F(InterpreterTest, Parameters) {
|
2017-07-20 00:14:59 +08:00
|
|
|
{
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN $2 + $`a b`", {{"2", 10}, {"a b", 15}});
|
2017-07-20 00:14:59 +08:00
|
|
|
ASSERT_EQ(stream.GetHeader().size(), 1U);
|
|
|
|
EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`");
|
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 25);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Not needed parameter.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream =
|
|
|
|
Interpret("RETURN $2 + $`a b`", {{"2", 10}, {"a b", 15}, {"c", 10}});
|
2017-07-20 00:14:59 +08:00
|
|
|
ASSERT_EQ(stream.GetHeader().size(), 1U);
|
|
|
|
EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`");
|
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 25);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, different parameters.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN $2 + $`a b`", {{"2", "da"}, {"a b", "ne"}});
|
2017-07-20 00:14:59 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0][0].Value<std::string>(), "dane");
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Non-primitive literal.
|
2017-12-22 20:39:31 +08:00
|
|
|
auto stream = Interpret("RETURN $2",
|
|
|
|
{{"2", std::vector<query::TypedValue>{5, 2, 3}}});
|
2017-07-20 00:14:59 +08:00
|
|
|
ASSERT_EQ(stream.GetResults().size(), 1U);
|
|
|
|
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
|
2017-08-21 21:44:35 +08:00
|
|
|
auto result = query::test_common::ToList<int64_t>(
|
2017-07-20 00:14:59 +08:00
|
|
|
stream.GetResults()[0][0].Value<std::vector<query::TypedValue>>());
|
|
|
|
ASSERT_THAT(result, testing::ElementsAre(5, 2, 3));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Cached ast, unprovided parameter.
|
2017-12-22 20:39:31 +08:00
|
|
|
ASSERT_THROW(Interpret("RETURN $2 + $`a b`", {{"2", "da"}, {"ab", "ne"}}),
|
2017-07-20 00:14:59 +08:00
|
|
|
query::UnprovidedParameterError);
|
|
|
|
}
|
|
|
|
}
|
2017-08-21 21:44:35 +08:00
|
|
|
|
|
|
|
// Test bfs end to end.
|
2017-12-22 20:39:31 +08:00
|
|
|
TEST_F(InterpreterTest, Bfs) {
|
2017-08-21 21:44:35 +08:00
|
|
|
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";
|
|
|
|
|
|
|
|
std::vector<std::vector<VertexAccessor>> levels(kNumLevels);
|
|
|
|
int id = 0;
|
|
|
|
|
|
|
|
// Set up.
|
|
|
|
{
|
2018-01-12 22:17:04 +08:00
|
|
|
database::GraphDbAccessor dba(db_);
|
2017-08-21 21:44:35 +08:00
|
|
|
auto add_node = [&](int level, bool reachable) {
|
2017-10-30 17:43:25 +08:00
|
|
|
auto node = dba.InsertVertex();
|
|
|
|
node.PropsSet(dba.Property(kId), id++);
|
|
|
|
node.PropsSet(dba.Property(kReachable), reachable);
|
2017-08-21 21:44:35 +08:00
|
|
|
levels[level].push_back(node);
|
|
|
|
return node;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto add_edge = [&](VertexAccessor &v1, VertexAccessor &v2,
|
|
|
|
bool reachable) {
|
2017-10-30 17:43:25 +08:00
|
|
|
auto edge = dba.InsertEdge(v1, v2, dba.EdgeType("edge"));
|
|
|
|
edge.PropsSet(dba.Property(kReachable), reachable);
|
2017-08-21 21:44:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2017-10-30 17:43:25 +08:00
|
|
|
dba.Commit();
|
2017-08-21 21:44:35 +08:00
|
|
|
}
|
|
|
|
|
2018-01-12 22:17:04 +08:00
|
|
|
database::GraphDbAccessor dba(db_);
|
2017-12-22 20:39:31 +08:00
|
|
|
ResultStreamFaker stream;
|
|
|
|
interpreter_(
|
|
|
|
"MATCH (n {id: 0})-[r *bfs..5 (e, n | n.reachable and "
|
|
|
|
"e.reachable)]->(m) RETURN r",
|
|
|
|
dba, {}, false)
|
|
|
|
.PullAll(stream);
|
2017-08-21 21:44:35 +08:00
|
|
|
|
|
|
|
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.
|
2017-12-22 20:39:31 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
edges[0].from().PropsAt(dba.Property(kId)).template Value<int64_t>(),
|
|
|
|
0);
|
2017-08-21 21:44:35 +08:00
|
|
|
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 =
|
2017-10-30 17:43:25 +08:00
|
|
|
edges.back().to().PropsAt(dba.Property(kId)).Value<int64_t>();
|
2017-08-21 21:44:35 +08:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-10 00:09:28 +08:00
|
|
|
|
2017-12-22 20:39:31 +08:00
|
|
|
TEST_F(InterpreterTest, CreateIndexInMulticommandTransaction) {
|
|
|
|
ResultStreamFaker stream;
|
2018-01-12 22:17:04 +08:00
|
|
|
database::GraphDbAccessor dba(db_);
|
2017-12-22 20:39:31 +08:00
|
|
|
ASSERT_THROW(
|
|
|
|
interpreter_("CREATE INDEX ON :X(y)", dba, {}, true).PullAll(stream),
|
|
|
|
query::IndexInMulticommandTxException);
|
2017-06-15 00:53:02 +08:00
|
|
|
}
|
2018-02-08 18:45:30 +08:00
|
|
|
|
|
|
|
// Test shortest path end to end.
|
|
|
|
TEST_F(InterpreterTest, ShortestPath) {
|
|
|
|
{
|
|
|
|
ResultStreamFaker stream;
|
|
|
|
database::GraphDbAccessor dba(db_);
|
|
|
|
interpreter_(
|
|
|
|
"CREATE (n:A {x: 1}), (m:B {x: 2}), (l:C {x: 1}), (n)-[:r1 {w: 1 "
|
|
|
|
"}]->(m)-[:r2 {w: 2}]->(l), (n)-[:r3 {w: 4}]->(l)",
|
|
|
|
dba, {}, true)
|
|
|
|
.PullAll(stream);
|
|
|
|
|
|
|
|
dba.Commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
ResultStreamFaker stream;
|
|
|
|
database::GraphDbAccessor dba(db_);
|
|
|
|
interpreter_("MATCH (n)-[e *wshortest 5 (e, n | e.w) ]->(m) return e", dba,
|
|
|
|
{}, false)
|
|
|
|
.PullAll(stream);
|
|
|
|
|
|
|
|
ASSERT_EQ(stream.GetHeader().size(), 1U);
|
|
|
|
EXPECT_EQ(stream.GetHeader()[0], "e");
|
|
|
|
ASSERT_EQ(stream.GetResults().size(), 3U);
|
|
|
|
|
|
|
|
std::vector<std::vector<std::string>> expected_results{
|
|
|
|
{"r1"}, {"r2"}, {"r1", "r2"}};
|
|
|
|
|
|
|
|
for (const auto &result : stream.GetResults()) {
|
|
|
|
const auto &edges =
|
|
|
|
query::test_common::ToList<EdgeAccessor>(result[0].ValueList());
|
|
|
|
|
|
|
|
std::vector<std::string> datum;
|
|
|
|
for (const auto &edge : edges) {
|
|
|
|
datum.push_back(dba.EdgeTypeName(edge.EdgeType()));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool any_match = false;
|
|
|
|
for (const auto &expected : expected_results) {
|
|
|
|
if (expected == datum) {
|
|
|
|
any_match = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPECT_TRUE(any_match);
|
|
|
|
}
|
|
|
|
}
|