Remove output formatters from vertex/edge accessors

Reviewers: teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2555
This commit is contained in:
Matej Ferencevic 2019-11-20 19:36:56 +01:00
parent d5f02c5ef8
commit 8be2b4af63
8 changed files with 233 additions and 151 deletions

View File

@ -70,10 +70,6 @@ class EdgeAccessor final {
bool operator==(const EdgeAccessor &e) const { return impl_ == e.impl_; }
bool operator!=(const EdgeAccessor &e) const { return !(*this == e); }
friend std::ostream &operator<<(std::ostream &s, const EdgeAccessor &e) {
throw utils::NotYetImplemented("operator<<");
}
};
class VertexAccessor final {
@ -178,10 +174,6 @@ class VertexAccessor final {
bool operator==(const VertexAccessor &v) const { return impl_ == v.impl_; }
bool operator!=(const VertexAccessor &v) const { return !(*this == v); }
friend std::ostream &operator<<(std::ostream &s, const VertexAccessor &v) {
throw utils::NotYetImplemented("operator<<");
}
};
inline VertexAccessor EdgeAccessor::To() const {
@ -285,10 +277,6 @@ class EdgeAccessor final {
bool operator==(const EdgeAccessor &e) const { return impl_ == e.impl_; }
bool operator!=(const EdgeAccessor &e) const { return !(*this == e); }
friend std::ostream &operator<<(std::ostream &s, const EdgeAccessor &e) {
return s << e.impl_;
}
};
class VertexAccessor final {
@ -471,10 +459,6 @@ class VertexAccessor final {
bool operator==(const VertexAccessor &v) const { return impl_ == v.impl_; }
bool operator!=(const VertexAccessor &v) const { return !(*this == v); }
friend std::ostream &operator<<(std::ostream &s, const VertexAccessor &v) {
return s << v.impl_;
}
};
inline VertexAccessor EdgeAccessor::To() const {

View File

@ -138,21 +138,6 @@ class Path {
return vertices_ == other.vertices_ && edges_ == other.edges_;
}
friend std::ostream &operator<<(std::ostream &os, const Path &path) {
DCHECK(path.vertices_.size() > 0U)
<< "Attempting to stream out an invalid path";
os << path.vertices_[0];
for (int i = 0; i < static_cast<int>(path.edges_.size()); i++) {
bool arrow_to_left = path.vertices_[i] == path.edges_[i].To();
if (arrow_to_left) os << "<";
os << "-" << path.edges_[i] << "-";
if (!arrow_to_left) os << ">";
os << path.vertices_[i + 1];
}
return os;
}
private:
// Contains all the vertices in the path.
utils::pmr::vector<VertexAccessor> vertices_;

View File

@ -9,7 +9,6 @@
#include "glog/logging.h"
#include "utils/algorithm.hpp"
#include "utils/exceptions.hpp"
#include "utils/hashing/fnv.hpp"
@ -288,39 +287,6 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
LOG(FATAL) << "Unsupported TypedValue::Type";
}
std::ostream &operator<<(std::ostream &os, const TypedValue &value) {
switch (value.type()) {
case TypedValue::Type::Null:
return os << "Null";
case TypedValue::Type::Bool:
return os << (value.ValueBool() ? "true" : "false");
case TypedValue::Type::Int:
return os << value.ValueInt();
case TypedValue::Type::Double:
return os << value.ValueDouble();
case TypedValue::Type::String:
return os << value.ValueString();
case TypedValue::Type::List:
os << "[";
utils::PrintIterable(os, value.ValueList());
return os << "]";
case TypedValue::Type::Map:
os << "{";
utils::PrintIterable(os, value.ValueMap(), ", ",
[](auto &stream, const auto &pair) {
stream << pair.first << ": " << pair.second;
});
return os << "}";
case TypedValue::Type::Vertex:
return os << value.ValueVertex();
case TypedValue::Type::Edge:
return os << value.ValueEdge();
case TypedValue::Type::Path:
return os << value.ValuePath();
}
LOG(FATAL) << "Unsupported PropertyValue::Type";
}
#define DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(type_param, typed_value_type, \
member) \
TypedValue &TypedValue::operator=(type_param other) { \

View File

@ -733,11 +733,4 @@ TypedValue operator%(const TypedValue &a, const TypedValue &b);
/** Output the TypedValue::Type value as a string */
std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type);
/**
* Output the TypedValue value as a readable string.
* Note that the primary use of this is for debugging and may not yield the
* pretty results you want to display to the user.
*/
std::ostream &operator<<(std::ostream &os, const TypedValue &value);
} // namespace query

View File

@ -17,6 +17,7 @@
#include "query/plan/planner.hpp"
#include "query/plan/pretty_print.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/string.hpp"
DEFINE_string(save_mock_db_file, "",
@ -168,15 +169,14 @@ class InteractiveDbAccessor {
return 0;
}
auto &value_vertex_count = property_value_vertex_count_[label_prop];
query::TypedValue tv_value(value);
if (value_vertex_count.find(tv_value) == value_vertex_count.end()) {
if (value_vertex_count.find(value) == value_vertex_count.end()) {
std::stringstream ss;
ss << tv_value;
ss << value;
int64_t count = ReadVertexCount("label '" + label + "' and property '" +
property + "' value '" + ss.str() + "'");
value_vertex_count[tv_value] = count;
value_vertex_count[value] = count;
}
return value_vertex_count.at(tv_value);
return value_vertex_count.at(value);
}
int64_t VerticesCount(
@ -301,7 +301,7 @@ class InteractiveDbAccessor {
}
DLOG(INFO) << "Load " << label << " " << property << " " << value_count;
for (int v = 0; v < value_count; ++v) {
auto value = LoadTypedValue(in);
auto value = LoadPropertyValue(in);
int64_t count;
in >> count;
if (in.fail()) {
@ -324,10 +324,8 @@ class InteractiveDbAccessor {
std::map<std::pair<std::string, std::string>, int64_t>
label_property_vertex_count_;
std::map<std::pair<std::string, std::string>, bool> label_property_index_;
std::map<
std::pair<std::string, std::string>,
std::unordered_map<query::TypedValue, int64_t, query::TypedValue::Hash,
query::TypedValue::BoolEqual>>
std::map<std::pair<std::string, std::string>,
std::map<storage::PropertyValue, int64_t>>
property_value_vertex_count_;
// TODO: Cache faked index counts by range.
@ -336,27 +334,27 @@ class InteractiveDbAccessor {
[&message]() { return ReadInt("Vertices with " + message + ": "); });
}
query::TypedValue LoadTypedValue(std::istream &in) {
storage::PropertyValue LoadPropertyValue(std::istream &in) {
std::string type;
in >> type;
if (type == "bool") {
return LoadTypedValue<bool>(in);
return LoadPropertyValue<bool>(in);
} else if (type == "int") {
return LoadTypedValue<int64_t>(in);
return LoadPropertyValue<int64_t>(in);
} else if (type == "double") {
return LoadTypedValue<double>(in);
return LoadPropertyValue<double>(in);
} else if (type == "string") {
return LoadTypedValue<std::string>(in);
return LoadPropertyValue<std::string>(in);
} else {
throw utils::BasicException("Unable to read type '{}'", type);
}
}
template <typename T>
query::TypedValue LoadTypedValue(std::istream &in) {
storage::PropertyValue LoadPropertyValue(std::istream &in) {
T val;
in >> val;
return query::TypedValue(val);
return storage::PropertyValue(val);
}
};

View File

@ -8,6 +8,8 @@
#include "query/plan/operator.hpp"
#include "query_common.hpp"
#include "formatters.hpp"
namespace query {
void PrintTo(const query::EdgeAtom::Direction &dir, std::ostream *os) {
switch (dir) {
@ -436,7 +438,8 @@ void BfsTest(Database *db, int lower_bound, int upper_bound,
query::TypedValue::BoolEqual{}(results[j][3], blocked))
++j;
SCOPED_TRACE(fmt::format("blocked entity = {}", blocked));
SCOPED_TRACE(
fmt::format("blocked entity = {}", ToString(blocked, execution_dba)));
// When an edge is blocked, it is blocked in both directions so we remove
// it before modifying edge list to account for direction and edge types;

119
tests/unit/formatters.hpp Normal file
View File

@ -0,0 +1,119 @@
#pragma once
#include <sstream>
#include <string>
#include <glog/logging.h>
#include "query/typed_value.hpp"
#include "utils/algorithm.hpp"
/// Functions that convert types to a `std::string` representation of it. The
/// `TAccessor` supplied must have the functions `NameToLabel`, `LabelToName`,
/// `NameToProperty`, `PropertyToName`, `NameToEdgeType` and `EdgeTypeToName`.
/// For example, both `storage::Storage` and `storage::Storage::Accessor` will
/// be apropriate.
template <class TAccessor>
inline std::string ToString(const query::VertexAccessor &vertex,
const TAccessor &acc) {
std::ostringstream os;
os << "V(";
auto maybe_labels = vertex.Labels(storage::View::NEW);
CHECK(maybe_labels.HasValue());
utils::PrintIterable(os, *maybe_labels, ":", [&](auto &stream, auto label) {
stream << acc.LabelToName(label);
});
if (maybe_labels->size() > 0) os << " ";
os << "{";
auto maybe_properties = vertex.Properties(storage::View::NEW);
CHECK(maybe_properties.HasValue());
utils::PrintIterable(
os, *maybe_properties, ", ", [&](auto &stream, const auto &pair) {
stream << acc.PropertyToName(pair.first) << ": " << pair.second;
});
os << "})";
return os.str();
}
template <class TAccessor>
inline std::string ToString(const query::EdgeAccessor &edge,
const TAccessor &acc) {
std::ostringstream os;
os << "E[" << acc.EdgeTypeToName(edge.EdgeType());
os << " {";
auto maybe_properties = edge.Properties(storage::View::NEW);
CHECK(maybe_properties.HasValue());
utils::PrintIterable(
os, *maybe_properties, ", ", [&](auto &stream, const auto &pair) {
stream << acc.PropertyToName(pair.first) << ": " << pair.second;
});
os << "}]";
return os.str();
}
template <class TAccessor>
inline std::string ToString(const query::Path &path, const TAccessor &acc) {
std::ostringstream os;
const auto &vertices = path.vertices();
const auto &edges = path.edges();
CHECK(vertices.size() > 0U) << "Attempting to stream out an invalid path";
os << ToString(vertices[0], acc);
for (size_t i = 0; i < edges.size(); ++i) {
bool arrow_to_left = vertices[i] == edges[i].To();
if (arrow_to_left) os << "<";
os << "-" << ToString(edges[i], acc) << "-";
if (!arrow_to_left) os << ">";
os << ToString(vertices[i + 1], acc);
}
return os.str();
}
template <class TAccessor>
inline std::string ToString(const query::TypedValue &value,
const TAccessor &acc) {
std::ostringstream os;
switch (value.type()) {
case query::TypedValue::Type::Null:
os << "null";
break;
case query::TypedValue::Type::Bool:
os << (value.ValueBool() ? "true" : "false");
break;
case query::TypedValue::Type::Int:
os << value.ValueInt();
break;
case query::TypedValue::Type::Double:
os << value.ValueDouble();
break;
case query::TypedValue::Type::String:
os << value.ValueString();
break;
case query::TypedValue::Type::List:
os << "[";
utils::PrintIterable(os, value.ValueList(), ", ",
[&](auto &stream, const auto &item) {
stream << ToString(item, acc);
});
os << "]";
break;
case query::TypedValue::Type::Map:
os << "{";
utils::PrintIterable(
os, value.ValueMap(), ", ", [&](auto &stream, const auto &pair) {
stream << pair.first << ": " << ToString(pair.second, acc);
});
os << "}";
break;
case query::TypedValue::Type::Vertex:
os << ToString(value.ValueVertex(), acc);
break;
case query::TypedValue::Type::Edge:
os << ToString(value.ValueEdge(), acc);
break;
case query::TypedValue::Type::Path:
os << ToString(value.ValuePath(), acc);
break;
}
return os.str();
}

View File

@ -9,31 +9,38 @@
#include "query_plan_common.hpp"
#include "formatters.hpp"
using namespace query::plan;
using query::AstStorage;
using Type = query::EdgeAtom::Type;
using Direction = query::EdgeAtom::Direction;
namespace std {
// Overloads for printing resulting rows from a query.
std::ostream &operator<<(std::ostream &stream,
const std::vector<TypedValue> &row) {
utils::PrintIterable(stream, row);
return stream;
// Functions for printing resulting rows from a query.
template <class TAccessor>
std::string ToString(const std::vector<TypedValue> &row, const TAccessor &acc) {
std::ostringstream os;
utils::PrintIterable(os, row, ", ", [&](auto &stream, const auto &item) {
stream << ToString(item, acc);
});
return os.str();
}
std::ostream &operator<<(std::ostream &stream,
const std::vector<std::vector<TypedValue>> &rows) {
utils::PrintIterable(stream, rows, "\n");
return stream;
template <class TAccessor>
std::string ToString(const std::vector<std::vector<TypedValue>> &rows,
const TAccessor &acc) {
std::ostringstream os;
utils::PrintIterable(os, rows, "\n", [&](auto &stream, const auto &item) {
stream << ToString(item, acc);
});
return os.str();
}
} // namespace std
namespace {
template <class TAccessor>
void AssertRows(const std::vector<std::vector<TypedValue>> &datum,
std::vector<std::vector<TypedValue>> expected) {
std::vector<std::vector<TypedValue>> expected,
const TAccessor &acc) {
auto row_equal = [](const auto &row1, const auto &row2) {
if (row1.size() != row2.size()) {
return false;
@ -51,19 +58,18 @@ void AssertRows(const std::vector<std::vector<TypedValue>> &datum,
ASSERT_TRUE(std::is_permutation(datum.begin(), datum.end(), expected.begin(),
expected.end(), row_equal))
<< "Actual rows:" << std::endl
<< datum << std::endl
<< ToString(datum, acc) << std::endl
<< "Expected rows:" << std::endl
<< expected;
<< ToString(expected, acc);
};
void CheckPlansProduce(
size_t expected_plan_count, query::CypherQuery *query, AstStorage &storage,
database::GraphDbAccessor *dba,
query::DbAccessor *dba,
std::function<void(const std::vector<std::vector<TypedValue>> &)> check) {
auto symbol_table = query::MakeSymbolTable(query);
query::DbAccessor execution_dba(dba);
auto planning_context =
MakePlanningContext(&storage, &symbol_table, query, &execution_dba);
MakePlanningContext(&storage, &symbol_table, query, dba);
auto query_parts = CollectQueryParts(symbol_table, storage, query);
EXPECT_TRUE(query_parts.query_parts.size() > 0);
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
@ -73,7 +79,7 @@ void CheckPlansProduce(
for (const auto &plan : plans) {
auto *produce = dynamic_cast<Produce *>(plan.get());
ASSERT_TRUE(produce);
auto context = MakeContext(storage, symbol_table, &execution_dba);
auto context = MakeContext(storage, symbol_table, dba);
auto results = CollectProduce(*produce, &context);
check(results);
}
@ -93,10 +99,13 @@ TEST(TestVariableStartPlanner, MatchReturn) {
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
RETURN("n")));
// We have 2 nodes `n` and `m` from which we could start, so expect 2 plans.
CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
2, query, storage, &execution_dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}},
execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
@ -117,10 +126,13 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n")));
// We have 3 nodes: `n`, `m` and `l` from which we could start.
CheckPlansProduce(3, query, storage, &dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
3, query, storage, &execution_dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}},
execution_dba);
});
}
{
// Equivalent to `MATCH (n) -[r]-> (m), (m) -[e]-> (l) RETURN n`.
@ -129,9 +141,12 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m")),
PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n")));
CheckPlansProduce(3, query, storage, &dba, [&](const auto &results) {
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
3, query, storage, &execution_dba, [&](const auto &results) {
AssertRows(results, {{TypedValue(query::VertexAccessor(v1))}},
execution_dba);
});
}
}
@ -153,15 +168,18 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) {
RETURN("n", "l")));
// We have 2 nodes `n` and `m` from which we could start the MATCH, and 2
// nodes for OPTIONAL MATCH. This should produce 2 * 2 plans.
CheckPlansProduce(4, query, storage, &dba, [&](const auto &results) {
// We expect to produce 2 rows:
// * (v1), (v3)
// * (v2), null
AssertRows(results,
{{TypedValue(query::VertexAccessor(v1)),
TypedValue(query::VertexAccessor(v3))},
{TypedValue(query::VertexAccessor(v2)), TypedValue()}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
4, query, storage, &execution_dba, [&](const auto &results) {
// We expect to produce 2 rows:
// * (v1), (v3)
// * (v2), null
AssertRows(results,
{{TypedValue(query::VertexAccessor(v1)),
TypedValue(query::VertexAccessor(v3))},
{TypedValue(query::VertexAccessor(v2)), TypedValue()}},
execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
@ -185,11 +203,15 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
RETURN("n", "m", "l", "u", "v")));
// Since MATCH, OPTIONAL MATCH and MERGE each have 2 nodes from which we can
// start, we generate 2 * 2 * 2 plans.
CheckPlansProduce(8, query, storage, &dba, [&](const auto &results) {
// We expect to produce a single row: (v1), (v2), null, (v1), (v2)
AssertRows(results, {{TypedValue(v1), TypedValue(v2), TypedValue(),
TypedValue(v1), TypedValue(v2)}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(8, query, storage, &execution_dba,
[&](const auto &results) {
// We expect to produce a single row: (v1), (v2), null, (v1), (v2)
AssertRows(results,
{{TypedValue(v1), TypedValue(v2), TypedValue(),
TypedValue(v1), TypedValue(v2)}},
execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
@ -209,10 +231,13 @@ TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
RETURN("n", "m", "l")));
// We can start from 2 nodes in each match. Since WITH separates query parts,
// we expect to get 2 plans for each, which totals 2 * 2.
CheckPlansProduce(4, query, storage, &dba, [&](const auto &results) {
// We expect to produce a single row: (v1), (v1), (v2)
AssertRows(results, {{TypedValue(v1), TypedValue(v1), TypedValue(v2)}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
4, query, storage, &execution_dba, [&](const auto &results) {
// We expect to produce a single row: (v1), (v1), (v2)
AssertRows(results, {{TypedValue(v1), TypedValue(v1), TypedValue(v2)}},
execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchVariableExpand) {
@ -236,9 +261,12 @@ TEST(TestVariableStartPlanner, MatchVariableExpand) {
// [r1, r2]
TypedValue r1_r2_list(
std::vector<TypedValue>{TypedValue(r1), TypedValue(r2)});
CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) {
AssertRows(results, {{r1_list}, {r2_list}, {r1_r2_list}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(2, query, storage, &execution_dba,
[&](const auto &results) {
AssertRows(results, {{r1_list}, {r2_list}, {r1_r2_list}},
execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
@ -266,9 +294,11 @@ TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)});
// [r2] (v2 -[*..2]-> v3)
TypedValue r2_list(std::vector<TypedValue>{TypedValue(r2)});
CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) {
AssertRows(results, {{r1_list}, {r2_list}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
2, query, storage, &execution_dba, [&](const auto &results) {
AssertRows(results, {{r1_list}, {r2_list}}, execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
@ -295,9 +325,11 @@ TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
// [r1, r2]
TypedValue r1_r2_list(
std::vector<TypedValue>{TypedValue(r1), TypedValue(r2)});
CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) {
AssertRows(results, {{r1_list}, {r1_r2_list}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(
2, query, storage, &execution_dba, [&](const auto &results) {
AssertRows(results, {{r1_list}, {r1_r2_list}}, execution_dba);
});
}
TEST(TestVariableStartPlanner, MatchBfs) {
@ -327,9 +359,11 @@ TEST(TestVariableStartPlanner, MatchBfs) {
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)}); // [r1]
CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) {
AssertRows(results, {{r1_list}});
});
query::DbAccessor execution_dba(&dba);
CheckPlansProduce(2, query, storage, &execution_dba,
[&](const auto &results) {
AssertRows(results, {{r1_list}}, execution_dba);
});
}
} // namespace