Query - Add expansion LogicalOp and tests

Summary: Query - AstTreeStorage - TODO for forwarding resolved

Reviewers: teon.banek, mislav.bradac, buda

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D139
This commit is contained in:
florijan 2017-03-21 11:25:44 +01:00
parent 848d022c60
commit a2a4c28168
7 changed files with 551 additions and 85 deletions

View File

@ -112,12 +112,16 @@ class NodeAtom : public PatternAtom {
protected:
using PatternAtom::PatternAtom;
};
class EdgeAtom : public PatternAtom {
friend class AstTreeStorage;
public:
// TODO change to IN, OUT, BOTH
// LEFT/RIGHT is not clear especially when expansion will not
// necessarily go from left to right
enum class Direction { LEFT, RIGHT, BOTH };
void Accept(TreeVisitorBase &visitor) override {
@ -127,12 +131,14 @@ class EdgeAtom : public PatternAtom {
}
Direction direction_ = Direction::BOTH;
std::vector<GraphDb::EdgeType> types_;
std::vector<GraphDb::EdgeType> edge_types_;
// TODO: change to unordered_map
std::map<GraphDb::Property, Expression *> properties_;
protected:
using PatternAtom::PatternAtom;
EdgeAtom(int uid, Identifier *identifier, Direction direction)
: PatternAtom(uid, identifier), direction_(direction) {}
};
class Clause : public Tree {
@ -241,8 +247,7 @@ class AstTreeStorage {
T *Create(Args &&... args) {
// Never call create for a Query. Call query() instead.
static_assert(!std::is_same<T, Query>::value, "Call query() instead");
// TODO: use std::forward here
T *p = new T(next_uid_++, args...);
T *p = new T(next_uid_++, std::forward<Args>(args)...);
storage_.emplace_back(p);
return p;
}

View File

@ -260,7 +260,7 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
users_identifiers.insert(variable);
}
if (ctx->relationshipDetail()->relationshipTypes()) {
edge->types_ = ctx->relationshipDetail()
edge->edge_types_ = ctx->relationshipDetail()
->relationshipTypes()
->accept(this)
.as<std::vector<GraphDb::EdgeType>>();

View File

@ -28,22 +28,22 @@ class LogicalOperator {
};
class CreateOp : public LogicalOperator {
public:
public:
CreateOp(NodeAtom* node_atom) : node_atom_(node_atom) {}
private:
private:
class CreateOpCursor : public Cursor {
public:
CreateOpCursor(CreateOp& self, GraphDbAccessor& db) : self_(self), db_(db) {}
public:
CreateOpCursor(CreateOp& self, GraphDbAccessor& db)
: self_(self), db_(db) {}
bool Pull(Frame &frame, SymbolTable &symbol_table) override {
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
if (!did_create_) {
auto new_node = db_.insert_vertex();
for (auto label : self_.node_atom_->labels_)
new_node.add_label(label);
for (auto label : self_.node_atom_->labels_) new_node.add_label(label);
ExpressionEvaluator evaluator(frame, symbol_table);
for (auto &kv : self_.node_atom_->properties_){
for (auto& kv : self_.node_atom_->properties_) {
kv.second->Accept(evaluator);
new_node.PropsSet(kv.first, evaluator.PopBack());
}
@ -54,24 +54,24 @@ private:
return false;
}
private:
CreateOp &self_;
GraphDbAccessor &db_;
private:
CreateOp& self_;
GraphDbAccessor& db_;
bool did_create_{false};
};
public:
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<CreateOpCursor>(*this, db);
}
private:
private:
NodeAtom* node_atom_ = nullptr;
};
class ScanAll : public LogicalOperator {
public:
ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {}
ScanAll(NodeAtom* node_atom) : node_atom_(node_atom) {}
private:
class ScanAllCursor : public Cursor {
@ -99,19 +99,226 @@ class ScanAll : public LogicalOperator {
}
private:
NodeAtom *node_atom_ = nullptr;
NodeAtom* node_atom_ = nullptr;
};
/**
* Expansion operator. For a node existing in the frame it
* expands one edge and one node and places them on the frame.
*
* This class does not handle node/edge filtering based on
* properties, labels and edge types. However, it does handle
* cycle filtering.
*
* Cycle filtering means that for a pattern that references
* the same node or edge in two places (for example (n)-->(n)),
* only expansions that match defined equalities are succesfully
* pulled.
*/
class Expand : public LogicalOperator {
using InEdgeT = decltype(std::declval<VertexAccessor>().in());
using InEdgeIteratorT = decltype(std::declval<VertexAccessor>().in().begin());
using OutEdgeT = decltype(std::declval<VertexAccessor>().out());
using OutEdgeIteratorT =
decltype(std::declval<VertexAccessor>().out().begin());
public:
/**
* Creates an expansion.
*
* Cycle-checking is controlled via booleans. A true value
* simply denotes that this expansion references an already
* Pulled node/edge, and should only be checked for equalities
* during expansion.
*
* @param node_atom Describes the node to be expanded. Only the
* identifier is used, labels and properties are ignored.
* @param edge_atom Describes the edge to be expanded. Identifier
* and direction are used, edge type and properties are ignored.
* @param input LogicalOperation that preceeds this one.
* @param input_symbol Symbol that points to a VertexAccessor
* in the Frame that expansion should emanate from.
* @param node_cycle If or not the node to be expanded is already
* present in the Frame and should just be checked for equality.
* @param edge_cycle Same like 'node_cycle', but for edges.
*/
Expand(NodeAtom* node_atom, EdgeAtom* edge_atom,
const std::shared_ptr<LogicalOperator>& input,
const Symbol& input_symbol,
bool node_cycle, bool edge_cycle)
: node_atom_(node_atom),
edge_atom_(edge_atom),
input_(input),
input_symbol_(input_symbol),
node_cycle_(node_cycle),
edge_cycle_(edge_cycle) {}
private:
class ExpandCursor : public Cursor {
public:
ExpandCursor(Expand& self, GraphDbAccessor& db)
: self_(self), input_cursor_(self.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (true) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
EdgeAccessor edge = *(*in_edges_it_)++;
if (HandleEdgeCycle(edge, frame, symbol_table) &&
PullNode(edge, EdgeAtom::Direction::LEFT, frame, symbol_table))
return true;
else
continue;
}
// attempt to get a value from the outgoing edges
if (out_edges_ && *out_edges_it_ != out_edges_->end()) {
EdgeAccessor edge = *(*out_edges_it_)++;
if (HandleEdgeCycle(edge, frame, symbol_table) &&
PullNode(edge, EdgeAtom::Direction::RIGHT, frame, symbol_table))
return true;
else
continue;
}
// if we are here, either the edges have not been initialized,
// or they have been exhausted. attempt to initialize the edges,
// if the input is exhausted
if (!InitEdges(frame, symbol_table)) return false;
// we have re-initialized the edges, continue with the loop
}
}
private:
Expand& self_;
std::unique_ptr<Cursor> input_cursor_;
// the iterable over edges and the current edge iterator are referenced via
// unique pointers because they can not be initialized in the constructor of
// this class. they are initialized once for each pull from the input
std::unique_ptr<InEdgeT> in_edges_;
std::unique_ptr<InEdgeIteratorT> in_edges_it_;
std::unique_ptr<OutEdgeT> out_edges_;
std::unique_ptr<OutEdgeIteratorT> out_edges_it_;
bool InitEdges(Frame& frame, SymbolTable& symbol_table) {
if (!input_cursor_->Pull(frame, symbol_table)) return false;
TypedValue vertex_value = frame[self_.input_symbol_];
auto vertex = vertex_value.Value<VertexAccessor>();
auto direction = self_.edge_atom_->direction_;
if (direction == EdgeAtom::Direction::LEFT ||
direction == EdgeAtom::Direction::BOTH) {
in_edges_ = std::make_unique<InEdgeT>(vertex.in());
in_edges_it_ = std::make_unique<InEdgeIteratorT>(in_edges_->begin());
}
if (direction == EdgeAtom::Direction::RIGHT ||
direction == EdgeAtom::Direction::BOTH) {
out_edges_ = std::make_unique<InEdgeT>(vertex.out());
out_edges_it_ = std::make_unique<InEdgeIteratorT>(out_edges_->begin());
}
// TODO add support for Front and Back expansion (when QueryPlanner
// will need it). For now only Back expansion (left to right) is
// supported
// TODO add support for named paths
// TODO add support for uniqueness (edge, vertex)
return true;
}
/**
* For a newly expanded edge handles cycle checking and frame insertion.
*
* @return If or not the given new_edge is a valid expansion. It is not
* valid only when doing an edge-cycle and the new_edge does not match the
* old.
*/
bool HandleEdgeCycle(EdgeAccessor& new_edge, Frame& frame,
SymbolTable& symbol_table) {
if (self_.edge_cycle_) {
TypedValue& old_edge_value =
frame[symbol_table[*self_.edge_atom_->identifier_]];
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
// not doing a cycle, so put the new_edge into the frame and return true
frame[symbol_table[*self_.edge_atom_->identifier_]] = new_edge;
return true;
}
}
/**
* Expands a node for the given newly expanded edge.
*
* @return True if after this call a new node has been successfully
* expanded. Returns false only when doing a node-cycle and the
* new node does not qualify.
*/
bool PullNode(EdgeAccessor& new_edge, EdgeAtom::Direction direction,
Frame& frame, SymbolTable& symbol_table) {
switch (direction) {
case EdgeAtom::Direction::LEFT:
return HandleNodeCycle(new_edge.from(), frame, symbol_table);
case EdgeAtom::Direction::RIGHT:
return HandleNodeCycle(new_edge.to(), frame, symbol_table);
case EdgeAtom::Direction::BOTH:
permanent_fail("Must indicate exact expansion direction here");
}
}
/**
* For a newly expanded node handles cycle checking and frame insertion.
*
* @return If or not the given new_node is a valid expansion. It is not
* valid only when doing a node-cycle and the new_node does not match the
* old.
*/
bool HandleNodeCycle(VertexAccessor new_node, Frame& frame,
SymbolTable& symbol_table) {
if (self_.node_cycle_) {
TypedValue& old_node_value =
frame[symbol_table[*self_.node_atom_->identifier_]];
return old_node_value.Value<VertexAccessor>() == new_node;
} else {
// not doing a cycle, so put the new_edge into the frame and return true
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
return true;
}
}
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<ExpandCursor>(*this, db);
}
private:
// info on what's getting expanded
NodeAtom* node_atom_;
EdgeAtom* edge_atom_;
// the input op and the symbol under which the op's result
// can be found in the frame
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
// if the given node and edge atom refer to symbols
// (query identifiers) that have already been expanded
// and should be just validated in the frame
bool node_cycle_;
bool edge_cycle_;
};
class NodeFilter : public LogicalOperator {
public:
NodeFilter(
std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
std::vector<GraphDb::Label> labels,
std::map<GraphDb::Property, Expression*> properties)
: input_(input),
input_symbol_(input_symbol),
labels_(labels),
properties_(properties) {}
NodeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
NodeAtom* node_atom)
: input_(input), input_symbol_(input_symbol), node_atom_(node_atom) {}
private:
class NodeFilterCursor : public Cursor {
@ -121,7 +328,7 @@ class NodeFilter : public LogicalOperator {
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (input_cursor_->Pull(frame, symbol_table)) {
const auto &vertex = frame[self_.input_symbol_].Value<VertexAccessor>();
const auto& vertex = frame[self_.input_symbol_].Value<VertexAccessor>();
if (VertexPasses(vertex, frame, symbol_table)) return true;
}
return false;
@ -133,11 +340,11 @@ class NodeFilter : public LogicalOperator {
bool VertexPasses(const VertexAccessor& vertex, Frame& frame,
SymbolTable& symbol_table) {
for (auto label : self_.labels_)
for (auto label : self_.node_atom_->labels_)
if (!vertex.has_label(label)) return false;
ExpressionEvaluator expression_evaluator(frame, symbol_table);
for (auto prop_pair : self_.properties_) {
for (auto prop_pair : self_.node_atom_->properties_) {
prop_pair.second->Accept(expression_evaluator);
TypedValue comparison_result =
vertex.PropsAt(prop_pair.first) == expression_evaluator.PopBack();
@ -157,8 +364,60 @@ class NodeFilter : public LogicalOperator {
private:
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
std::vector<GraphDb::Label> labels_;
std::map<GraphDb::Property, Expression*> properties_;
NodeAtom* node_atom_;
};
class EdgeFilter : public LogicalOperator {
public:
EdgeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
EdgeAtom* edge_atom)
: input_(input), input_symbol_(input_symbol), edge_atom_(edge_atom) {}
private:
class EdgeFilterCursor : public Cursor {
public:
EdgeFilterCursor(EdgeFilter& self, GraphDbAccessor& db)
: self_(self), input_cursor_(self_.input_->MakeCursor(db)) {}
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
while (input_cursor_->Pull(frame, symbol_table)) {
const auto& edge = frame[self_.input_symbol_].Value<EdgeAccessor>();
if (EdgePasses(edge, frame, symbol_table)) return true;
}
return false;
}
private:
EdgeFilter& self_;
std::unique_ptr<Cursor> input_cursor_;
bool EdgePasses(const EdgeAccessor& edge, Frame& frame,
SymbolTable& symbol_table) {
for (auto edge_type : self_.edge_atom_->edge_types_)
if (edge.edge_type() != edge_type) return false;
ExpressionEvaluator expression_evaluator(frame, symbol_table);
for (auto prop_pair : self_.edge_atom_->properties_) {
prop_pair.second->Accept(expression_evaluator);
TypedValue comparison_result =
edge.PropsAt(prop_pair.first) == expression_evaluator.PopBack();
if (comparison_result.type() == TypedValue::Type::Null ||
!comparison_result.Value<bool>())
return false;
}
return true;
}
};
public:
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
return std::make_unique<EdgeFilterCursor>(*this, db);
}
private:
std::shared_ptr<LogicalOperator> input_;
const Symbol input_symbol_;
EdgeAtom* edge_atom_;
};
class Produce : public LogicalOperator {

View File

@ -44,7 +44,7 @@ LogicalOperator *GenMatch(
if (!node_atom->labels_.empty() || !node_atom->properties_.empty()) {
auto &input_symbol = symbol_table.at(*node_atom->identifier_);
return new NodeFilter(std::shared_ptr<LogicalOperator>(scan_all), input_symbol,
node_atom->labels_, node_atom->properties_);
node_atom);
}
return scan_all;
}

View File

@ -3,6 +3,8 @@
#include <set>
#include <vector>
#include "cppitertools/chain.hpp"
#include "database/graph_db.hpp"
#include "storage/record_accessor.hpp"
#include "storage/vertex.hpp"

View File

@ -103,7 +103,7 @@ TEST(CypherMainVisitorTest, RelationshipPatternDetails) {
auto *edge = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
ASSERT_EQ(edge->direction_, EdgeAtom::Direction::LEFT);
ASSERT_THAT(
edge->types_,
edge->edge_types_,
UnorderedElementsAre(ast_generator.db_accessor_->edge_type("type1"),
ast_generator.db_accessor_->edge_type("type2")));
// TODO: test properties

View File

@ -6,6 +6,7 @@
#include <memory>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "communication/result_stream_faker.hpp"
@ -46,8 +47,7 @@ auto CollectProduce(std::shared_ptr<Produce> produce, SymbolTable &symbol_table,
auto cursor = produce->MakeCursor(db_accessor);
while (cursor->Pull(frame, symbol_table)) {
std::vector<TypedValue> values;
for (auto &symbol : symbols)
values.emplace_back(frame[symbol]);
for (auto &symbol : symbols) values.emplace_back(frame[symbol]);
stream.Result(values);
}
@ -65,10 +65,6 @@ void ExecuteCreate(std::shared_ptr<CreateOp> create, GraphDbAccessor &db) {
}
}
auto MakeScanAll(NodeAtom *node_atom) {
return std::make_shared<ScanAll>(node_atom);
}
template <typename... TNamedExpressions>
auto MakeProduce(std::shared_ptr<LogicalOperator> input,
TNamedExpressions... named_expressions) {
@ -76,6 +72,43 @@ auto MakeProduce(std::shared_ptr<LogicalOperator> input,
input, std::vector<NamedExpression *>{named_expressions...});
}
/**
* Creates and returns a tuple of stuff for a scan-all starting
* from the node with the given name.
*
* Returns (node_atom, scan_all_logical_op, symbol).
*/
auto MakeScanAll(AstTreeStorage &ast_storage, SymbolTable &symbol_table,
const std::string &identifier) {
auto node =
ast_storage.Create<NodeAtom>(ast_storage.Create<Identifier>(identifier));
auto logical_op = std::make_shared<ScanAll>(node);
auto symbol = symbol_table.CreateSymbol(identifier);
symbol_table[*node->identifier_] = symbol;
return std::make_tuple(node, logical_op, symbol);
}
auto MakeExpand(AstTreeStorage &ast_storage, SymbolTable &symbol_table,
std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
const std::string &edge_identifier,
EdgeAtom::Direction direction, bool edge_cycle,
const std::string &node_identifier, bool node_cycle) {
auto edge = ast_storage.Create<EdgeAtom>(
ast_storage.Create<Identifier>(edge_identifier), direction);
auto edge_sym = symbol_table.CreateSymbol(edge_identifier);
symbol_table[*edge->identifier_] = edge_sym;
auto node = ast_storage.Create<NodeAtom>(
ast_storage.Create<Identifier>(node_identifier));
auto node_sym = symbol_table.CreateSymbol(node_identifier);
symbol_table[*node->identifier_] = node_sym;
auto op = std::make_shared<Expand>(node, edge, input, input_symbol,
node_cycle, edge_cycle);
return std::make_tuple(edge, edge_sym, node, node_sym, op);
}
/*
* Actual tests start here.
*/
@ -89,21 +122,13 @@ TEST(Interpreter, MatchReturn) {
dba->insert_vertex();
AstTreeStorage storage;
SymbolTable symbol_table;
// make a scan all
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
auto scan_all = MakeScanAll(node);
// make a named expression and a produce
auto scan_all = MakeScanAll(storage, symbol_table, "n");
auto output =
storage.Create<NamedExpression>("n", storage.Create<Identifier>("n"));
auto produce = MakeProduce(scan_all, output);
// fill up the symbol table
SymbolTable symbol_table;
auto n_symbol = symbol_table.CreateSymbol("n");
symbol_table[*node->identifier_] = n_symbol;
symbol_table[*output->expression_] = n_symbol;
auto produce = MakeProduce(std::get<1>(scan_all), output);
symbol_table[*output->expression_] = std::get<2>(scan_all);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
@ -135,31 +160,27 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) {
v5.PropsSet(property, 1);
AstTreeStorage storage;
SymbolTable symbol_table;
// make a scan all
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
auto scan_all = MakeScanAll(node);
auto n = MakeScanAll(storage, symbol_table, "n");
std::get<0>(n)->labels_.emplace_back(label);
// TODO set a property once int-literal expressions are available
// node filtering
SymbolTable symbol_table;
auto n_symbol = symbol_table.CreateSymbol("n");
// TODO implement the test once int-literal expressions are available
auto node_filter = std::make_shared<NodeFilter>(
scan_all, n_symbol, std::vector<GraphDb::Label>{label},
std::map<GraphDb::Property, Expression*>{});
std::get<1>(n), std::get<2>(n), std::get<0>(n));
// make a named expression and a produce
auto output =
storage.Create<NamedExpression>("x", storage.Create<Identifier>("n"));
symbol_table[*output->expression_] = std::get<2>(n);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
auto produce = MakeProduce(node_filter, output);
// fill up the symbol table
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
symbol_table[*node->identifier_] = n_symbol;
symbol_table[*output->expression_] = n_symbol;
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
EXPECT_EQ(result.GetResults().size(), 1);
// TODO change expected value to 1 once literal expressions are avaialable
EXPECT_EQ(result.GetResults().size(), 3);
}
TEST(Interpreter, NodeFilterMultipleLabels) {
@ -171,34 +192,34 @@ TEST(Interpreter, NodeFilterMultipleLabels) {
GraphDb::Label label2 = dba->label("label2");
GraphDb::Label label3 = dba->label("label3");
// the test will look for nodes that have label1 and label2
dba->insert_vertex(); // NOT accepted
dba->insert_vertex().add_label(label1); // NOT accepted
dba->insert_vertex().add_label(label2); // NOT accepted
dba->insert_vertex().add_label(label3); // NOT accepted
auto v1 = dba->insert_vertex(); // YES accepted
dba->insert_vertex(); // NOT accepted
dba->insert_vertex().add_label(label1); // NOT accepted
dba->insert_vertex().add_label(label2); // NOT accepted
dba->insert_vertex().add_label(label3); // NOT accepted
auto v1 = dba->insert_vertex(); // YES accepted
v1.add_label(label1);
v1.add_label(label2);
auto v2 = dba->insert_vertex(); // NOT accepted
auto v2 = dba->insert_vertex(); // NOT accepted
v2.add_label(label1);
v2.add_label(label3);
auto v3 = dba->insert_vertex(); // YES accepted
auto v3 = dba->insert_vertex(); // YES accepted
v3.add_label(label1);
v3.add_label(label2);
v3.add_label(label3);
AstTreeStorage storage;
SymbolTable symbol_table;
// make a scan all
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
auto scan_all = MakeScanAll(node);
auto n = MakeScanAll(storage, symbol_table, "n");
std::get<0>(n)->labels_.emplace_back(label1);
std::get<0>(n)->labels_.emplace_back(label2);
// TODO set a property once int-literal expressions are available
// node filtering
SymbolTable symbol_table;
auto n_symbol = symbol_table.CreateSymbol("n");
// TODO implement the test once int-literal expressions are available
auto node_filter = std::make_shared<NodeFilter>(
scan_all, n_symbol, std::vector<GraphDb::Label>{label1, label2},
std::map<GraphDb::Property, Expression *>());
std::get<1>(n), std::get<2>(n), std::get<0>(n));
// make a named expression and a produce
auto output =
@ -207,8 +228,7 @@ TEST(Interpreter, NodeFilterMultipleLabels) {
// fill up the symbol table
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
symbol_table[*node->identifier_] = n_symbol;
symbol_table[*output->expression_] = n_symbol;
symbol_table[*output->expression_] = std::get<2>(n);
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
EXPECT_EQ(result.GetResults().size(), 2);
@ -226,7 +246,6 @@ TEST(Interpreter, CreateNodeWithAttributes) {
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
node->labels_.emplace_back(label);
// TODO make a property here with an int literal expression
// node->properties_[property] = TypedValue(42);
auto create = std::make_shared<CreateOp>(node);
ExecuteCreate(create, *dba);
@ -237,10 +256,191 @@ TEST(Interpreter, CreateNodeWithAttributes) {
vertex_count++;
EXPECT_EQ(vertex.labels().size(), 1);
EXPECT_EQ(*vertex.labels().begin(), label);
EXPECT_EQ(vertex.Properties().size(), 1);
auto prop_eq = vertex.PropsAt(property) == TypedValue(42);
ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
EXPECT_TRUE(prop_eq.Value<bool>());
// TODO re-enable these tests once int literal becomes available
// EXPECT_EQ(vertex.Properties().size(), 1);
// auto prop_eq = vertex.PropsAt(property) == TypedValue(42);
// ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
// EXPECT_TRUE(prop_eq.Value<bool>());
}
EXPECT_EQ(vertex_count, 1);
}
TEST(Interpreter, Expand) {
Dbms dbms;
auto dba = dbms.active();
// make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
auto v1 = dba->insert_vertex();
v1.add_label((GraphDb::Label)1);
auto v2 = dba->insert_vertex();
v2.add_label((GraphDb::Label)2);
auto v3 = dba->insert_vertex();
v3.add_label((GraphDb::Label)3);
auto edge_type = dba->edge_type("Edge");
dba->insert_edge(v1, v2, edge_type);
dba->insert_edge(v1, v3, edge_type);
AstTreeStorage storage;
SymbolTable symbol_table;
auto test_expand = [&](EdgeAtom::Direction direction, int expected_result_count) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n),
"r", direction, false, "m", false);
// make a named expression and a produce
auto output =
storage.Create<NamedExpression>("m", storage.Create<Identifier>("m"));
symbol_table[*output->expression_] = std::get<3>(r_m);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
auto produce = MakeProduce(std::get<4>(r_m), output);
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
EXPECT_EQ(result.GetResults().size(), expected_result_count);
};
test_expand(EdgeAtom::Direction::RIGHT, 2);
test_expand(EdgeAtom::Direction::LEFT, 2);
test_expand(EdgeAtom::Direction::BOTH, 4);
}
TEST(Interpreter, ExpandNodeCycle) {
Dbms dbms;
auto dba = dbms.active();
// make a graph (v1)->(v2) that
// has a recursive edge (v1)->(v1)
auto v1 = dba->insert_vertex();
auto v2 = dba->insert_vertex();
auto edge_type = dba->edge_type("Edge");
dba->insert_edge(v1, v1, edge_type);
dba->insert_edge(v1, v2, edge_type);
AstTreeStorage storage;
SymbolTable symbol_table;
auto test_cycle = [&](bool with_cycle, int expected_result_count) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_n =
MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n), "r",
EdgeAtom::Direction::RIGHT, false, "n", with_cycle);
if (with_cycle)
symbol_table[*std::get<2>(r_n)->identifier_] =
symbol_table[*std::get<0>(n)->identifier_];
// make a named expression and a produce
auto output =
storage.Create<NamedExpression>("n", storage.Create<Identifier>("n"));
symbol_table[*output->expression_] = std::get<2>(n);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
auto produce = MakeProduce(std::get<4>(r_n), output);
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
EXPECT_EQ(result.GetResults().size(), expected_result_count);
};
test_cycle(true, 1);
test_cycle(false, 2);
}
TEST(Interpreter, ExpandEdgeCycle) {
Dbms dbms;
auto dba = dbms.active();
// make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
auto v1 = dba->insert_vertex();
v1.add_label((GraphDb::Label)1);
auto v2 = dba->insert_vertex();
v2.add_label((GraphDb::Label)2);
auto v3 = dba->insert_vertex();
v3.add_label((GraphDb::Label)3);
auto edge_type = dba->edge_type("Edge");
dba->insert_edge(v1, v2, edge_type);
dba->insert_edge(v1, v3, edge_type);
AstTreeStorage storage;
SymbolTable symbol_table;
auto test_cycle = [&](bool with_cycle, int expected_result_count) {
auto i = MakeScanAll(storage, symbol_table, "i");
auto r_j = MakeExpand(storage, symbol_table, std::get<1>(i), std::get<2>(i),
"r", EdgeAtom::Direction::BOTH, false, "j", false);
auto r_k =
MakeExpand(storage, symbol_table, std::get<4>(r_j), std::get<3>(r_j),
"r", EdgeAtom::Direction::BOTH, with_cycle, "k", false);
if (with_cycle)
symbol_table[*std::get<0>(r_k)->identifier_] =
symbol_table[*std::get<0>(r_j)->identifier_];
// make a named expression and a produce
auto output =
storage.Create<NamedExpression>("r", storage.Create<Identifier>("r"));
symbol_table[*output->expression_] = std::get<1>(r_j);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
auto produce = MakeProduce(std::get<4>(r_k), output);
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
EXPECT_EQ(result.GetResults().size(), expected_result_count);
};
test_cycle(true, 4);
test_cycle(false, 6);
}
TEST(Interpreter, EdgeFilter) {
Dbms dbms;
auto dba = dbms.active();
// make an N-star expanding from (v1)
// where only one edge will qualify
// and there are all combinations of
// (edge_type yes|no) * (property yes|absent|no)
std::vector<GraphDb::EdgeType> edge_types;
for (int j = 0; j < 2; ++j)
edge_types.push_back(dba->edge_type("et" + std::to_string(j)));
std::vector<VertexAccessor> vertices;
for (int i = 0; i < 7; ++i) vertices.push_back(dba->insert_vertex());
GraphDb::Property prop = dba->property("prop");
std::vector<EdgeAccessor> edges;
for (int i = 0; i < 6; ++i) {
edges.push_back(
dba->insert_edge(vertices[0], vertices[i + 1], edge_types[i % 2]));
switch (i % 3) {
case 0:
edges.back().PropsSet(prop, 42);
break;
case 1:
edges.back().PropsSet(prop, 100);
break;
default:
break;
}
}
AstTreeStorage storage;
SymbolTable symbol_table;
// define an operator tree for query
// MATCH (n)-[r]->(m) RETURN m
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n),
"r", EdgeAtom::Direction::RIGHT, false, "m", false);
std::get<0>(r_m)->edge_types_.push_back(edge_types[0]);
// TODO when int literal expression becomes available
// add a property filter
auto edge_filter = std::make_shared<EdgeFilter>(
std::get<4>(r_m), std::get<1>(r_m), std::get<0>(r_m));
// make a named expression and a produce
auto output =
storage.Create<NamedExpression>("m", storage.Create<Identifier>("m"));
symbol_table[*output->expression_] = std::get<3>(r_m);
symbol_table[*output] = symbol_table.CreateSymbol("named_expression_1");
auto produce = MakeProduce(edge_filter, output);
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
// TODO change the expected value to 1 once property filtering is available
EXPECT_EQ(result.GetResults().size(), 3);
}