4f417e1f5d
Summary: Previously, our implementation of the Bolt protocol buffered all results in memory before sending them out to the client. This implementation immediately streams the results to the client to avoid any memory allocations. Also, this implementation splits the interpretation and pulling logic into two. Reviewers: teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1495
97 lines
3.0 KiB
C++
97 lines
3.0 KiB
C++
// tests in this suite deal with edge cases in logical operator behavior
|
|
// that's not easily testable with single-phase testing. instead, for
|
|
// easy testing and latter readability they are tested end-to-end.
|
|
|
|
#include <experimental/optional>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "communication/result_stream_faker.hpp"
|
|
#include "database/graph_db.hpp"
|
|
#include "database/graph_db_accessor.hpp"
|
|
#include "query/interpreter.hpp"
|
|
|
|
DECLARE_bool(query_cost_planner);
|
|
|
|
class QueryExecution : public testing::Test {
|
|
protected:
|
|
std::experimental::optional<database::SingleNode> db_;
|
|
std::experimental::optional<database::GraphDbAccessor> dba_;
|
|
|
|
void SetUp() {
|
|
db_.emplace();
|
|
dba_.emplace(*db_);
|
|
}
|
|
|
|
void TearDown() {
|
|
dba_ = std::experimental::nullopt;
|
|
db_ = std::experimental::nullopt;
|
|
}
|
|
|
|
/** Commits the current transaction and refreshes the dba_
|
|
* variable to hold a new accessor with a new transaction */
|
|
void Commit() {
|
|
dba_->Commit();
|
|
dba_.emplace(*db_);
|
|
}
|
|
|
|
/** Executes the query and returns the results.
|
|
* Does NOT commit the transaction */
|
|
auto Execute(const std::string &query) {
|
|
ResultStreamFaker<query::TypedValue> stream;
|
|
auto results = query::Interpreter{*db_}(query, *dba_, {}, false);
|
|
stream.Header(results.header());
|
|
results.PullAll(stream);
|
|
stream.Summary(results.summary());
|
|
return stream.GetResults();
|
|
}
|
|
};
|
|
|
|
TEST_F(QueryExecution, MissingOptionalIntoExpand) {
|
|
// validating bug where expanding from Null (due to a preceeding optional
|
|
// match) exhausts the expansion cursor, even if it's input is still not
|
|
// exhausted
|
|
Execute(
|
|
"CREATE (a:Person {id: 1}), (b:Person "
|
|
"{id:2})-[:Has]->(:Dog)-[:Likes]->(:Food )");
|
|
Commit();
|
|
ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 4);
|
|
|
|
auto Exec = [this](bool desc, const std::string &edge_pattern) {
|
|
// this test depends on left-to-right query planning
|
|
FLAGS_query_cost_planner = false;
|
|
return Execute(std::string("MATCH (p:Person) WITH p ORDER BY p.id ") +
|
|
(desc ? "DESC " : "") +
|
|
"OPTIONAL MATCH (p)-->(d:Dog) WITH p, d "
|
|
"MATCH (d)" +
|
|
edge_pattern +
|
|
"(f:Food) "
|
|
"RETURN p, d, f")
|
|
.size();
|
|
};
|
|
|
|
std::string expand = "-->";
|
|
std::string variable = "-[*1]->";
|
|
std::string bfs = "-[*bfs..1]->";
|
|
|
|
EXPECT_EQ(Exec(false, expand), 1);
|
|
EXPECT_EQ(Exec(true, expand), 1);
|
|
EXPECT_EQ(Exec(false, variable), 1);
|
|
EXPECT_EQ(Exec(true, bfs), 1);
|
|
EXPECT_EQ(Exec(true, bfs), 1);
|
|
}
|
|
|
|
TEST_F(QueryExecution, EdgeUniquenessInOptional) {
|
|
// Validating that an edge uniqueness check can't fail when the edge is Null
|
|
// due to optonal match. Since edge-uniqueness only happens in one OPTIONAL
|
|
// MATCH, we only need to check that scenario.
|
|
Execute("CREATE (), ()-[:Type]->()");
|
|
Commit();
|
|
ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 3);
|
|
EXPECT_EQ(Execute("MATCH (n) OPTIONAL MATCH (n)-[r1]->(), (n)-[r2]->() "
|
|
"RETURN n, r1, r2")
|
|
.size(),
|
|
3);
|
|
}
|