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:
parent
848d022c60
commit
a2a4c28168
@ -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;
|
||||
}
|
||||
|
@ -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>>();
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user