memgraph/tests/unit/query_plan_bag_semantics.cpp
florijan 6fc6a27288 Refactor GraphDb
Summary:
GraphDb is refactored to become an API exposing different parts
necessary for the database to function. These different parts can have
different implementations in SingleNode or distributed Master/Server
GraphDb implementations.

Interally GraphDb is implemented using two class heirarchies. One
contains all the members and correct wiring for each situation. The
other takes care of initialization and shutdown. This architecture is
practical because it can guarantee that the initialization of the
object structure is complete, before initializing state.

Reviewers: buda, mislav.bradac, dgleich, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1093
2018-01-12 16:47:24 +01:00

267 lines
8.7 KiB
C++

//
// Copyright 2017 Memgraph
// Created by Florijan Stamenkovic on 14.03.17.
//
#include <algorithm>
#include <iterator>
#include <memory>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "communication/result_stream_faker.hpp"
#include "query/context.hpp"
#include "query/exceptions.hpp"
#include "query/plan/operator.hpp"
#include "query_plan_common.hpp"
using namespace query;
using namespace query::plan;
TEST(QueryPlan, Skip) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n1");
auto skip = std::make_shared<plan::Skip>(n.op_, LITERAL(2));
EXPECT_EQ(0, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(0, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(0, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(1, PullAll(skip, dba, symbol_table));
for (int i = 0; i < 10; ++i) dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(11, PullAll(skip, dba, symbol_table));
}
TEST(QueryPlan, Limit) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n1");
auto skip = std::make_shared<plan::Limit>(n.op_, LITERAL(2));
EXPECT_EQ(0, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(1, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(2, PullAll(skip, dba, symbol_table));
dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(2, PullAll(skip, dba, symbol_table));
for (int i = 0; i < 10; ++i) dba.InsertVertex();
dba.AdvanceCommand();
EXPECT_EQ(2, PullAll(skip, dba, symbol_table));
}
TEST(QueryPlan, CreateLimit) {
// CREATE (n), (m)
// MATCH (n) CREATE (m) LIMIT 1
// in the end we need to have 3 vertices in the db
database::SingleNode db;
database::GraphDbAccessor dba(db);
dba.InsertVertex();
dba.InsertVertex();
dba.AdvanceCommand();
AstTreeStorage storage;
SymbolTable symbol_table;
auto n = MakeScanAll(storage, symbol_table, "n1");
auto m = NODE("m");
symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m", true);
auto c = std::make_shared<CreateNode>(m, n.op_);
auto skip = std::make_shared<plan::Limit>(c, LITERAL(1));
EXPECT_EQ(1, PullAll(skip, dba, symbol_table));
dba.AdvanceCommand();
EXPECT_EQ(3, CountIterable(dba.Vertices(false)));
}
TEST(QueryPlan, OrderBy) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto prop = dba.Property("prop");
// contains a series of tests
// each test defines the ordering a vector of values in the desired order
auto Null = TypedValue::Null;
std::vector<std::pair<Ordering, std::vector<TypedValue>>> orderable{
{Ordering::ASC, {0, 0, 0.5, 1, 2, 12.6, 42, Null, Null}},
{Ordering::ASC, {false, false, true, true, Null, Null}},
{Ordering::ASC, {"A", "B", "a", "a", "aa", "ab", "aba", Null, Null}},
{Ordering::DESC, {Null, Null, 33, 33, 32.5, 32, 2.2, 2.1, 0}},
{Ordering::DESC, {Null, true, false}},
{Ordering::DESC, {Null, "zorro", "borro"}}};
for (const auto &order_value_pair : orderable) {
const auto &values = order_value_pair.second;
// empty database
for (auto &vertex : dba.Vertices(false)) dba.DetachRemoveVertex(vertex);
dba.AdvanceCommand();
ASSERT_EQ(0, CountIterable(dba.Vertices(false)));
// take some effort to shuffle the values
// because we are testing that something not ordered gets ordered
// and need to take care it does not happen by accident
std::vector<TypedValue> shuffled(values.begin(), values.end());
auto order_equal = [&values, &shuffled]() {
return std::equal(values.begin(), values.end(), shuffled.begin(),
TypedValue::BoolEqual{});
};
for (int i = 0; i < 50 && order_equal(); ++i) {
std::random_shuffle(shuffled.begin(), shuffled.end());
}
ASSERT_FALSE(order_equal());
// create the vertices
for (const auto &value : shuffled) dba.InsertVertex().PropsSet(prop, value);
dba.AdvanceCommand();
// order by and collect results
auto n = MakeScanAll(storage, symbol_table, "n");
auto n_p = PROPERTY_LOOKUP("n", prop);
symbol_table[*n_p->expression_] = n.sym_;
auto order_by = std::make_shared<plan::OrderBy>(
n.op_,
std::vector<std::pair<Ordering, Expression *>>{
{order_value_pair.first, n_p}},
std::vector<Symbol>{n.sym_});
auto n_p_ne = NEXPR("n.p", n_p);
symbol_table[*n_p_ne] = symbol_table.CreateSymbol("n.p", true);
auto produce = MakeProduce(order_by, n_p_ne);
auto results = CollectProduce(produce.get(), symbol_table, dba);
ASSERT_EQ(values.size(), results.size());
for (int j = 0; j < results.size(); ++j)
EXPECT_TRUE(TypedValue::BoolEqual{}(results[j][0], values[j]));
}
}
TEST(QueryPlan, OrderByMultiple) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto p1 = dba.Property("p1");
auto p2 = dba.Property("p2");
// create a bunch of vertices that in two properties
// have all the variations (with repetition) of N values.
// ensure that those vertices are not created in the
// "right" sequence, but randomized
const int N = 20;
std::vector<std::pair<int, int>> prop_values;
for (int i = 0; i < N * N; ++i) prop_values.emplace_back(i % N, i / N);
std::random_shuffle(prop_values.begin(), prop_values.end());
for (const auto &pair : prop_values) {
auto v = dba.InsertVertex();
v.PropsSet(p1, pair.first);
v.PropsSet(p2, pair.second);
}
dba.AdvanceCommand();
// order by and collect results
auto n = MakeScanAll(storage, symbol_table, "n");
auto n_p1 = PROPERTY_LOOKUP("n", p1);
symbol_table[*n_p1->expression_] = n.sym_;
auto n_p2 = PROPERTY_LOOKUP("n", p2);
symbol_table[*n_p2->expression_] = n.sym_;
// order the results so we get
// (p1: 0, p2: N-1)
// (p1: 0, p2: N-2)
// ...
// (p1: N-1, p2:0)
auto order_by = std::make_shared<plan::OrderBy>(
n.op_,
std::vector<std::pair<Ordering, Expression *>>{
{Ordering::ASC, n_p1}, {Ordering::DESC, n_p2},
},
std::vector<Symbol>{n.sym_});
auto n_p1_ne = NEXPR("n.p1", n_p1);
symbol_table[*n_p1_ne] = symbol_table.CreateSymbol("n.p1", true);
auto n_p2_ne = NEXPR("n.p2", n_p2);
symbol_table[*n_p2_ne] = symbol_table.CreateSymbol("n.p2", true);
auto produce = MakeProduce(order_by, n_p1_ne, n_p2_ne);
auto results = CollectProduce(produce.get(), symbol_table, dba);
ASSERT_EQ(N * N, results.size());
for (int j = 0; j < N * N; ++j) {
ASSERT_EQ(results[j][0].type(), TypedValue::Type::Int);
EXPECT_EQ(results[j][0].Value<int64_t>(), j / N);
ASSERT_EQ(results[j][1].type(), TypedValue::Type::Int);
EXPECT_EQ(results[j][1].Value<int64_t>(), N - 1 - j % N);
}
}
TEST(QueryPlan, OrderByExceptions) {
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstTreeStorage storage;
SymbolTable symbol_table;
auto prop = dba.Property("prop");
// a vector of pairs of typed values that should result
// in an exception when trying to order on them
std::vector<std::pair<TypedValue, TypedValue>> exception_pairs{
{42, true},
{42, "bla"},
{42, std::vector<TypedValue>{42}},
{true, "bla"},
{true, std::vector<TypedValue>{true}},
{"bla", std::vector<TypedValue>{"bla"}},
// illegal comparisons of same-type values
{std::vector<TypedValue>{42}, std::vector<TypedValue>{42}}};
for (const auto &pair : exception_pairs) {
// empty database
for (auto &vertex : dba.Vertices(false)) dba.DetachRemoveVertex(vertex);
dba.AdvanceCommand();
ASSERT_EQ(0, CountIterable(dba.Vertices(false)));
// make two vertices, and set values
dba.InsertVertex().PropsSet(prop, pair.first);
dba.InsertVertex().PropsSet(prop, pair.second);
dba.AdvanceCommand();
ASSERT_EQ(2, CountIterable(dba.Vertices(false)));
for (const auto &va : dba.Vertices(false))
ASSERT_NE(va.PropsAt(prop).type(), PropertyValue::Type::Null);
// order by and expect an exception
auto n = MakeScanAll(storage, symbol_table, "n");
auto n_p = PROPERTY_LOOKUP("n", prop);
symbol_table[*n_p->expression_] = n.sym_;
auto order_by = std::make_shared<plan::OrderBy>(
n.op_,
std::vector<std::pair<Ordering, Expression *>>{{Ordering::ASC, n_p}},
std::vector<Symbol>{});
EXPECT_THROW(PullAll(order_by, dba, symbol_table), QueryRuntimeException);
}
}