Query - ExpandCreate, prop tests, MATCH_CREATE
Summary: Query - ExpandCreate op added (with tests). LogicalOp tests have property enabled. MATCH-CREATE test started but disabled due to MVCC. Reviewers: buda, mislav.bradac, teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D150
This commit is contained in:
parent
33eaf80344
commit
981f93d575
@ -59,11 +59,9 @@ class Record : public Version<T> {
|
|||||||
// Mike Olson says 17 march 1993: the tests in this routine are correct;
|
// Mike Olson says 17 march 1993: the tests in this routine are correct;
|
||||||
// if you think they're not, you're wrong, and you should think about it
|
// if you think they're not, you're wrong, and you should think about it
|
||||||
// again. i know, it happened to me.
|
// again. i know, it happened to me.
|
||||||
// This implementation is different than the original one by the <= between
|
|
||||||
// cmd.cre() and t.cid, instead of just <. This is the behaviour we want.
|
|
||||||
|
|
||||||
return ((tx.cre() == t.id && // inserted by the current transaction
|
return ((tx.cre() == t.id && // inserted by the current transaction
|
||||||
cmd.cre() <= t.cid && // before or during this command, and
|
cmd.cre() < t.cid && // before this command, and
|
||||||
(tx.exp() == Id(0) || // the row has not been deleted, or
|
(tx.exp() == Id(0) || // the row has not been deleted, or
|
||||||
(tx.exp() == t.id && // it was deleted by the current
|
(tx.exp() == t.id && // it was deleted by the current
|
||||||
// transaction
|
// transaction
|
||||||
@ -114,9 +112,13 @@ class Record : public Version<T> {
|
|||||||
|
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
// True if this record is visible for write.
|
// True if this record is visible for write.
|
||||||
|
// Note that this logic is different from the one above
|
||||||
|
// in the sense that a record is visible if created before
|
||||||
|
// OR DURING this command. this is done to support cypher's
|
||||||
|
// queries which can match, update and return in the same query
|
||||||
bool is_visible_write(const tx::Transaction &t) {
|
bool is_visible_write(const tx::Transaction &t) {
|
||||||
return (tx.cre() == t.id && // inserted by the current transaction
|
return (tx.cre() == t.id && // inserted by the current transaction
|
||||||
cmd.cre() <= t.cid && // before this command, and
|
cmd.cre() <= t.cid && // before OR DURING this command, and
|
||||||
(tx.exp() == Id(0) || // the row has not been deleted, or
|
(tx.exp() == Id(0) || // the row has not been deleted, or
|
||||||
(tx.exp() == t.id && // it was deleted by the current
|
(tx.exp() == t.id && // it was deleted by the current
|
||||||
// transaction
|
// transaction
|
||||||
|
@ -77,6 +77,10 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Visit(Literal &literal) override {
|
||||||
|
result_stack_.push_back(literal.value_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Frame &frame_;
|
Frame &frame_;
|
||||||
SymbolTable &symbol_table_;
|
SymbolTable &symbol_table_;
|
||||||
|
@ -40,6 +40,9 @@ class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class CreateOp : public LogicalOperator {
|
class CreateOp : public LogicalOperator {
|
||||||
|
// TODO add an optional input that (if given) gets pulled
|
||||||
|
// and the create op gets executed for each success
|
||||||
|
// TODO rename to CreateSingle or CreateNode
|
||||||
public:
|
public:
|
||||||
CreateOp(NodeAtom* node_atom) : node_atom_(node_atom) {}
|
CreateOp(NodeAtom* node_atom) : node_atom_(node_atom) {}
|
||||||
DEFVISITABLE(LogicalOperatorVisitor);
|
DEFVISITABLE(LogicalOperatorVisitor);
|
||||||
@ -61,6 +64,8 @@ class CreateOp : public LogicalOperator {
|
|||||||
new_node.PropsSet(kv.first, evaluator.PopBack());
|
new_node.PropsSet(kv.first, evaluator.PopBack());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frame[symbol_table[*self_.node_atom_->identifier_]] = new_node;
|
||||||
|
|
||||||
did_create_ = true;
|
did_create_ = true;
|
||||||
return true;
|
return true;
|
||||||
} else
|
} else
|
||||||
@ -82,6 +87,117 @@ class CreateOp : public LogicalOperator {
|
|||||||
NodeAtom* node_atom_ = nullptr;
|
NodeAtom* node_atom_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CreateExpand : public LogicalOperator {
|
||||||
|
|
||||||
|
public:
|
||||||
|
CreateExpand(NodeAtom* node_atom, EdgeAtom* edge_atom,
|
||||||
|
const std::shared_ptr<LogicalOperator>& input,
|
||||||
|
const Symbol& input_symbol, bool node_existing)
|
||||||
|
: node_atom_(node_atom),
|
||||||
|
edge_atom_(edge_atom),
|
||||||
|
input_(input),
|
||||||
|
input_symbol_(input_symbol),
|
||||||
|
node_existing_(node_existing) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
class CreateExpandCursor : public Cursor {
|
||||||
|
public:
|
||||||
|
CreateExpandCursor(CreateExpand& self, GraphDbAccessor& db)
|
||||||
|
: self_(self), db_(db), input_cursor_(self.input_->MakeCursor(db)) {}
|
||||||
|
|
||||||
|
bool Pull(Frame& frame, SymbolTable& symbol_table) override {
|
||||||
|
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||||
|
|
||||||
|
// get the origin vertex
|
||||||
|
TypedValue vertex_value = frame[self_.input_symbol_];
|
||||||
|
auto v1 = vertex_value.Value<VertexAccessor>();
|
||||||
|
|
||||||
|
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||||
|
|
||||||
|
// get the destination vertex (possibly an existing node)
|
||||||
|
VertexAccessor v2 = OtherVertex(frame, symbol_table, evaluator);
|
||||||
|
|
||||||
|
// create an edge between the two nodes
|
||||||
|
switch (self_.edge_atom_->direction_) {
|
||||||
|
case EdgeAtom::Direction::LEFT:
|
||||||
|
CreateEdge(v2, v1, evaluator);
|
||||||
|
break;
|
||||||
|
case EdgeAtom::Direction::RIGHT:
|
||||||
|
CreateEdge(v1, v2, evaluator);
|
||||||
|
break;
|
||||||
|
case EdgeAtom::Direction::BOTH:
|
||||||
|
permanent_fail("Undefined direction not allowed in create");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CreateExpand& self_;
|
||||||
|
GraphDbAccessor& db_;
|
||||||
|
std::unique_ptr<Cursor> input_cursor_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for getting an existing node or creating a new one.
|
||||||
|
* @return The newly created or already existing node.
|
||||||
|
*/
|
||||||
|
VertexAccessor OtherVertex(Frame &frame, SymbolTable &symbol_table,
|
||||||
|
ExpressionEvaluator &evaluator) {
|
||||||
|
if (self_.node_existing_) {
|
||||||
|
TypedValue& dest_node_value =
|
||||||
|
frame[symbol_table[*self_.node_atom_->identifier_]];
|
||||||
|
return dest_node_value.Value<VertexAccessor>();
|
||||||
|
} else {
|
||||||
|
// the node does not exist, it needs to be created
|
||||||
|
auto node = db_.insert_vertex();
|
||||||
|
for (auto label : self_.node_atom_->labels_) node.add_label(label);
|
||||||
|
for (auto kv : self_.node_atom_->properties_) {
|
||||||
|
kv.second->Accept(evaluator);
|
||||||
|
node.PropsSet(kv.first, evaluator.PopBack());
|
||||||
|
}
|
||||||
|
frame[symbol_table[*self_.node_atom_->identifier_]] = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for creating an edge.
|
||||||
|
*
|
||||||
|
* @param from Origin vertex of the edge.
|
||||||
|
* @param to Destination vertex of the edge.
|
||||||
|
* @param evaluator Expression evaluator for property value eval.
|
||||||
|
*/
|
||||||
|
void CreateEdge(VertexAccessor& from, VertexAccessor& to,
|
||||||
|
ExpressionEvaluator& evaluator) {
|
||||||
|
EdgeAccessor edge =
|
||||||
|
db_.insert_edge(from, to, self_.edge_atom_->edge_types_[0]);
|
||||||
|
for (auto kv : self_.edge_atom_->properties_) {
|
||||||
|
kv.second->Accept(evaluator);
|
||||||
|
edge.PropsSet(kv.first, evaluator.PopBack());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor& db) override {
|
||||||
|
return std::make_unique<CreateExpandCursor>(*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 atom refers to an existing node
|
||||||
|
// (either matched or created)
|
||||||
|
bool node_existing_;
|
||||||
|
};
|
||||||
|
|
||||||
class ScanAll : public LogicalOperator {
|
class ScanAll : public LogicalOperator {
|
||||||
public:
|
public:
|
||||||
ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {}
|
ScanAll(NodeAtom *node_atom) : node_atom_(node_atom) {}
|
||||||
|
@ -34,9 +34,11 @@ TEST(GraphDbAccessorTest, InsertVertex) {
|
|||||||
EXPECT_EQ(CountVertices(*accessor), 0);
|
EXPECT_EQ(CountVertices(*accessor), 0);
|
||||||
|
|
||||||
accessor->insert_vertex();
|
accessor->insert_vertex();
|
||||||
|
accessor->advance_command();
|
||||||
EXPECT_EQ(CountVertices(*accessor), 1);
|
EXPECT_EQ(CountVertices(*accessor), 1);
|
||||||
|
|
||||||
accessor->insert_vertex();
|
accessor->insert_vertex();
|
||||||
|
accessor->advance_command();
|
||||||
EXPECT_EQ(CountVertices(*accessor), 2);
|
EXPECT_EQ(CountVertices(*accessor), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +49,7 @@ TEST(GraphDbAccessorTest, RemoveVertexSameTransaction) {
|
|||||||
EXPECT_EQ(CountVertices(*accessor), 0);
|
EXPECT_EQ(CountVertices(*accessor), 0);
|
||||||
|
|
||||||
auto va1 = accessor->insert_vertex();
|
auto va1 = accessor->insert_vertex();
|
||||||
|
accessor->advance_command();
|
||||||
EXPECT_EQ(CountVertices(*accessor), 1);
|
EXPECT_EQ(CountVertices(*accessor), 1);
|
||||||
|
|
||||||
EXPECT_TRUE(accessor->remove_vertex(va1));
|
EXPECT_TRUE(accessor->remove_vertex(va1));
|
||||||
@ -81,6 +84,7 @@ TEST(GraphDbAccessorTest, InsertEdge) {
|
|||||||
|
|
||||||
auto va1 = dba->insert_vertex();
|
auto va1 = dba->insert_vertex();
|
||||||
auto va2 = dba->insert_vertex();
|
auto va2 = dba->insert_vertex();
|
||||||
|
dba->advance_command();
|
||||||
EXPECT_EQ(va1.in_degree(), 0);
|
EXPECT_EQ(va1.in_degree(), 0);
|
||||||
EXPECT_EQ(va1.out_degree(), 0);
|
EXPECT_EQ(va1.out_degree(), 0);
|
||||||
EXPECT_EQ(va2.in_degree(), 0);
|
EXPECT_EQ(va2.in_degree(), 0);
|
||||||
@ -88,6 +92,7 @@ TEST(GraphDbAccessorTest, InsertEdge) {
|
|||||||
|
|
||||||
// setup (v1) - [:likes] -> (v2)
|
// setup (v1) - [:likes] -> (v2)
|
||||||
dba->insert_edge(va1, va2, dba->edge_type("likes"));
|
dba->insert_edge(va1, va2, dba->edge_type("likes"));
|
||||||
|
dba->advance_command();
|
||||||
EXPECT_EQ(CountEdges(*dba), 1);
|
EXPECT_EQ(CountEdges(*dba), 1);
|
||||||
EXPECT_EQ(va1.out().begin()->to(), va2);
|
EXPECT_EQ(va1.out().begin()->to(), va2);
|
||||||
EXPECT_EQ(va2.in().begin()->from(), va1);
|
EXPECT_EQ(va2.in().begin()->from(), va1);
|
||||||
@ -99,6 +104,7 @@ TEST(GraphDbAccessorTest, InsertEdge) {
|
|||||||
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
||||||
auto va3 = dba->insert_vertex();
|
auto va3 = dba->insert_vertex();
|
||||||
dba->insert_edge(va3, va2, dba->edge_type("hates"));
|
dba->insert_edge(va3, va2, dba->edge_type("hates"));
|
||||||
|
dba->advance_command();
|
||||||
EXPECT_EQ(CountEdges(*dba), 2);
|
EXPECT_EQ(CountEdges(*dba), 2);
|
||||||
EXPECT_EQ(va3.out().begin()->to(), va2);
|
EXPECT_EQ(va3.out().begin()->to(), va2);
|
||||||
EXPECT_EQ(va1.in_degree(), 0);
|
EXPECT_EQ(va1.in_degree(), 0);
|
||||||
@ -111,35 +117,33 @@ TEST(GraphDbAccessorTest, InsertEdge) {
|
|||||||
|
|
||||||
TEST(GraphDbAccessorTest, RemoveEdge) {
|
TEST(GraphDbAccessorTest, RemoveEdge) {
|
||||||
Dbms dbms;
|
Dbms dbms;
|
||||||
auto dba1 = dbms.active();
|
auto dba = dbms.active();
|
||||||
|
|
||||||
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
||||||
auto va1 = dba1->insert_vertex();
|
auto va1 = dba->insert_vertex();
|
||||||
auto va2 = dba1->insert_vertex();
|
auto va2 = dba->insert_vertex();
|
||||||
auto va3 = dba1->insert_vertex();
|
auto va3 = dba->insert_vertex();
|
||||||
dba1->insert_edge(va1, va2, dba1->edge_type("likes"));
|
dba->insert_edge(va1, va2, dba->edge_type("likes"));
|
||||||
dba1->insert_edge(va3, va2, dba1->edge_type("hates"));
|
dba->insert_edge(va3, va2, dba->edge_type("hates"));
|
||||||
EXPECT_EQ(CountEdges(*dba1), 2);
|
dba->advance_command();
|
||||||
|
EXPECT_EQ(CountEdges(*dba), 2);
|
||||||
|
|
||||||
// remove all [:hates] edges
|
// remove all [:hates] edges
|
||||||
dba1->commit();
|
EXPECT_EQ(CountEdges(*dba), 2);
|
||||||
auto dba2 = dbms.active();
|
for (auto edge : dba->edges())
|
||||||
EXPECT_EQ(CountEdges(*dba2), 2);
|
if (edge.edge_type() == dba->edge_type("hates")) dba->remove_edge(edge);
|
||||||
for (auto edge : dba2->edges())
|
|
||||||
if (edge.edge_type() == dba2->edge_type("hates")) dba2->remove_edge(edge);
|
|
||||||
|
|
||||||
// current state: (v1) - [:likes] -> (v2), (v3)
|
// current state: (v1) - [:likes] -> (v2), (v3)
|
||||||
dba2->commit();
|
dba->advance_command();
|
||||||
auto dba3 = dbms.active();
|
EXPECT_EQ(CountEdges(*dba), 1);
|
||||||
EXPECT_EQ(CountEdges(*dba3), 1);
|
EXPECT_EQ(CountVertices(*dba), 3);
|
||||||
EXPECT_EQ(CountVertices(*dba3), 3);
|
for (auto edge : dba->edges()) {
|
||||||
for (auto edge : dba3->edges()) {
|
EXPECT_EQ(edge.edge_type(), dba->edge_type("likes"));
|
||||||
EXPECT_EQ(edge.edge_type(), dba3->edge_type("likes"));
|
|
||||||
auto v1 = edge.from();
|
auto v1 = edge.from();
|
||||||
auto v2 = edge.to();
|
auto v2 = edge.to();
|
||||||
|
|
||||||
// ensure correct connectivity for all the vertices
|
// ensure correct connectivity for all the vertices
|
||||||
for (auto vertex : dba3->vertices()) {
|
for (auto vertex : dba->vertices()) {
|
||||||
if (vertex == v1) {
|
if (vertex == v1) {
|
||||||
EXPECT_EQ(vertex.in_degree(), 0);
|
EXPECT_EQ(vertex.in_degree(), 0);
|
||||||
EXPECT_EQ(vertex.out_degree(), 1);
|
EXPECT_EQ(vertex.out_degree(), 1);
|
||||||
@ -156,60 +160,57 @@ TEST(GraphDbAccessorTest, RemoveEdge) {
|
|||||||
|
|
||||||
TEST(GraphDbAccessorTest, DetachRemoveVertex) {
|
TEST(GraphDbAccessorTest, DetachRemoveVertex) {
|
||||||
Dbms dbms;
|
Dbms dbms;
|
||||||
auto dba1 = dbms.active();
|
auto dba = dbms.active();
|
||||||
|
|
||||||
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
// setup (v1) - [:likes] -> (v2) <- [:hates] - (v3)
|
||||||
auto va1 = dba1->insert_vertex();
|
auto va1 = dba->insert_vertex();
|
||||||
auto va2 = dba1->insert_vertex();
|
auto va2 = dba->insert_vertex();
|
||||||
auto va3 = dba1->insert_vertex();
|
auto va3 = dba->insert_vertex();
|
||||||
dba1->insert_edge(va1, va2, dba1->edge_type("likes"));
|
dba->insert_edge(va1, va2, dba->edge_type("likes"));
|
||||||
dba1->insert_edge(va1, va3, dba1->edge_type("likes"));
|
dba->insert_edge(va1, va3, dba->edge_type("likes"));
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
// ensure that plain remove does NOT work
|
// ensure that plain remove does NOT work
|
||||||
EXPECT_EQ(CountVertices(*dba1), 3);
|
EXPECT_EQ(CountVertices(*dba), 3);
|
||||||
EXPECT_EQ(CountEdges(*dba1), 2);
|
EXPECT_EQ(CountEdges(*dba), 2);
|
||||||
EXPECT_FALSE(dba1->remove_vertex(va1));
|
EXPECT_FALSE(dba->remove_vertex(va1));
|
||||||
EXPECT_FALSE(dba1->remove_vertex(va2));
|
EXPECT_FALSE(dba->remove_vertex(va2));
|
||||||
EXPECT_FALSE(dba1->remove_vertex(va3));
|
EXPECT_FALSE(dba->remove_vertex(va3));
|
||||||
EXPECT_EQ(CountVertices(*dba1), 3);
|
EXPECT_EQ(CountVertices(*dba), 3);
|
||||||
EXPECT_EQ(CountEdges(*dba1), 2);
|
EXPECT_EQ(CountEdges(*dba), 2);
|
||||||
|
|
||||||
// make a new transaction because at the moment deletions
|
// make a new transaction because at the moment deletions
|
||||||
// in the same transaction are not visible
|
// in the same transaction are not visible
|
||||||
// DETACH REMOVE V3
|
// DETACH REMOVE V3
|
||||||
// new situation: (v1) - [:likes] -> (v2)
|
// new situation: (v1) - [:likes] -> (v2)
|
||||||
dba1->detach_remove_vertex(va3);
|
dba->detach_remove_vertex(va3);
|
||||||
dba1->commit();
|
dba->advance_command();
|
||||||
auto dba2 = dbms.active();
|
|
||||||
|
|
||||||
EXPECT_EQ(CountVertices(*dba2), 2);
|
EXPECT_EQ(CountVertices(*dba), 2);
|
||||||
EXPECT_EQ(CountEdges(*dba2), 1);
|
EXPECT_EQ(CountEdges(*dba), 1);
|
||||||
for (auto va : dba2->vertices()) EXPECT_FALSE(dba2->remove_vertex(va));
|
for (auto va : dba->vertices()) EXPECT_FALSE(dba->remove_vertex(va));
|
||||||
|
|
||||||
dba2->commit();
|
dba->advance_command();
|
||||||
auto dba3 = dbms.active();
|
EXPECT_EQ(CountVertices(*dba), 2);
|
||||||
EXPECT_EQ(CountVertices(*dba3), 2);
|
EXPECT_EQ(CountEdges(*dba), 1);
|
||||||
EXPECT_EQ(CountEdges(*dba3), 1);
|
|
||||||
|
|
||||||
for (auto va : dba3->vertices()) {
|
for (auto va : dba->vertices()) {
|
||||||
EXPECT_FALSE(dba3->remove_vertex(va));
|
EXPECT_FALSE(dba->remove_vertex(va));
|
||||||
dba3->detach_remove_vertex(va);
|
dba->detach_remove_vertex(va);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dba3->commit();
|
dba->advance_command();
|
||||||
auto dba4 = dbms.active();
|
EXPECT_EQ(CountVertices(*dba), 1);
|
||||||
EXPECT_EQ(CountVertices(*dba4), 1);
|
EXPECT_EQ(CountEdges(*dba), 0);
|
||||||
EXPECT_EQ(CountEdges(*dba4), 0);
|
|
||||||
|
|
||||||
// remove the last vertex, it has no connections
|
// remove the last vertex, it has no connections
|
||||||
// so that should work
|
// so that should work
|
||||||
for (auto va : dba4->vertices()) EXPECT_TRUE(dba4->remove_vertex(va));
|
for (auto va : dba->vertices()) EXPECT_TRUE(dba->remove_vertex(va));
|
||||||
|
|
||||||
dba4->commit();
|
dba->advance_command();
|
||||||
auto dba5 = dbms.active();
|
EXPECT_EQ(CountVertices(*dba), 0);
|
||||||
EXPECT_EQ(CountVertices(*dba5), 0);
|
EXPECT_EQ(CountEdges(*dba), 0);
|
||||||
EXPECT_EQ(CountEdges(*dba5), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GraphDbAccessorTest, Labels) {
|
TEST(GraphDbAccessorTest, Labels) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// Created by Florijan Stamenkovic on 14.03.17.
|
// Created by Florijan Stamenkovic on 14.03.17.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -56,8 +57,8 @@ auto CollectProduce(std::shared_ptr<Produce> produce, SymbolTable &symbol_table,
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExecuteCreate(std::shared_ptr<CreateOp> create, GraphDbAccessor &db) {
|
void ExecuteCreate(std::shared_ptr<LogicalOperator> create, GraphDbAccessor &db,
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table) {
|
||||||
Frame frame(symbol_table.max_position());
|
Frame frame(symbol_table.max_position());
|
||||||
auto cursor = create->MakeCursor(db);
|
auto cursor = create->MakeCursor(db);
|
||||||
while (cursor->Pull(frame, symbol_table)) {
|
while (cursor->Pull(frame, symbol_table)) {
|
||||||
@ -109,6 +110,11 @@ auto MakeExpand(AstTreeStorage &ast_storage, SymbolTable &symbol_table,
|
|||||||
return std::make_tuple(edge, edge_sym, node, node_sym, op);
|
return std::make_tuple(edge, edge_sym, node, node_sym, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TIterable>
|
||||||
|
auto CountIterable(TIterable iterable) {
|
||||||
|
return std::distance(iterable.begin(), iterable.end());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actual tests start here.
|
* Actual tests start here.
|
||||||
*/
|
*/
|
||||||
@ -120,6 +126,7 @@ TEST(Interpreter, MatchReturn) {
|
|||||||
// add a few nodes to the database
|
// add a few nodes to the database
|
||||||
dba->insert_vertex();
|
dba->insert_vertex();
|
||||||
dba->insert_vertex();
|
dba->insert_vertex();
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
@ -158,6 +165,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) {
|
|||||||
v2.PropsSet(property, 1);
|
v2.PropsSet(property, 1);
|
||||||
v4.PropsSet(property, 42);
|
v4.PropsSet(property, 42);
|
||||||
v5.PropsSet(property, 1);
|
v5.PropsSet(property, 1);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
@ -165,7 +173,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) {
|
|||||||
// make a scan all
|
// make a scan all
|
||||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||||
std::get<0>(n)->labels_.emplace_back(label);
|
std::get<0>(n)->labels_.emplace_back(label);
|
||||||
// TODO set a property once int-literal expressions are available
|
std::get<0>(n)->properties_[property] = storage.Create<Literal>(42);
|
||||||
|
|
||||||
// node filtering
|
// node filtering
|
||||||
auto node_filter = std::make_shared<NodeFilter>(
|
auto node_filter = std::make_shared<NodeFilter>(
|
||||||
@ -179,8 +187,7 @@ TEST(Interpreter, NodeFilterLabelsAndProperties) {
|
|||||||
auto produce = MakeProduce(node_filter, output);
|
auto produce = MakeProduce(node_filter, output);
|
||||||
|
|
||||||
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
|
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
|
||||||
// TODO change expected value to 1 once literal expressions are avaialable
|
EXPECT_EQ(result.GetResults().size(), 1);
|
||||||
EXPECT_EQ(result.GetResults().size(), 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Interpreter, NodeFilterMultipleLabels) {
|
TEST(Interpreter, NodeFilterMultipleLabels) {
|
||||||
@ -206,6 +213,7 @@ TEST(Interpreter, NodeFilterMultipleLabels) {
|
|||||||
v3.add_label(label1);
|
v3.add_label(label1);
|
||||||
v3.add_label(label2);
|
v3.add_label(label2);
|
||||||
v3.add_label(label3);
|
v3.add_label(label3);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
@ -214,10 +222,8 @@ TEST(Interpreter, NodeFilterMultipleLabels) {
|
|||||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||||
std::get<0>(n)->labels_.emplace_back(label1);
|
std::get<0>(n)->labels_.emplace_back(label1);
|
||||||
std::get<0>(n)->labels_.emplace_back(label2);
|
std::get<0>(n)->labels_.emplace_back(label2);
|
||||||
// TODO set a property once int-literal expressions are available
|
|
||||||
|
|
||||||
// node filtering
|
// node filtering
|
||||||
// TODO implement the test once int-literal expressions are available
|
|
||||||
auto node_filter = std::make_shared<NodeFilter>(
|
auto node_filter = std::make_shared<NodeFilter>(
|
||||||
std::get<1>(n), std::get<2>(n), std::get<0>(n));
|
std::get<1>(n), std::get<2>(n), std::get<0>(n));
|
||||||
|
|
||||||
@ -242,13 +248,16 @@ TEST(Interpreter, CreateNodeWithAttributes) {
|
|||||||
GraphDb::Property property = dba->label("age");
|
GraphDb::Property property = dba->label("age");
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
|
SymbolTable symbol_table;
|
||||||
|
|
||||||
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
|
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
|
||||||
|
symbol_table[*node->identifier_] = symbol_table.CreateSymbol("n");
|
||||||
node->labels_.emplace_back(label);
|
node->labels_.emplace_back(label);
|
||||||
// TODO make a property here with an int literal expression
|
node->properties_[property] = storage.Create<Literal>(42);
|
||||||
|
|
||||||
auto create = std::make_shared<CreateOp>(node);
|
auto create = std::make_shared<CreateOp>(node);
|
||||||
ExecuteCreate(create, *dba);
|
ExecuteCreate(create, *dba, symbol_table);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
// count the number of vertices
|
// count the number of vertices
|
||||||
int vertex_count = 0;
|
int vertex_count = 0;
|
||||||
@ -256,15 +265,185 @@ TEST(Interpreter, CreateNodeWithAttributes) {
|
|||||||
vertex_count++;
|
vertex_count++;
|
||||||
EXPECT_EQ(vertex.labels().size(), 1);
|
EXPECT_EQ(vertex.labels().size(), 1);
|
||||||
EXPECT_EQ(*vertex.labels().begin(), label);
|
EXPECT_EQ(*vertex.labels().begin(), label);
|
||||||
// TODO re-enable these tests once int literal becomes available
|
EXPECT_EQ(vertex.Properties().size(), 1);
|
||||||
// EXPECT_EQ(vertex.Properties().size(), 1);
|
auto prop_eq = vertex.PropsAt(property) == TypedValue(42);
|
||||||
// auto prop_eq = vertex.PropsAt(property) == TypedValue(42);
|
ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
|
||||||
// ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
|
EXPECT_TRUE(prop_eq.Value<bool>());
|
||||||
// EXPECT_TRUE(prop_eq.Value<bool>());
|
|
||||||
}
|
}
|
||||||
EXPECT_EQ(vertex_count, 1);
|
EXPECT_EQ(vertex_count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Interpreter, CreateReturn) {
|
||||||
|
// test CREATE (n:Person {age: 42}) RETURN n, n.age
|
||||||
|
Dbms dbms;
|
||||||
|
auto dba = dbms.active();
|
||||||
|
|
||||||
|
GraphDb::Label label = dba->label("Person");
|
||||||
|
GraphDb::Property property = dba->label("age");
|
||||||
|
|
||||||
|
AstTreeStorage storage;
|
||||||
|
SymbolTable symbol_table;
|
||||||
|
|
||||||
|
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
|
||||||
|
auto sym_n = symbol_table.CreateSymbol("n");
|
||||||
|
symbol_table[*node->identifier_] = sym_n;
|
||||||
|
node->labels_.emplace_back(label);
|
||||||
|
node->properties_[property] = storage.Create<Literal>(42);
|
||||||
|
|
||||||
|
auto create = std::make_shared<CreateOp>(node);
|
||||||
|
auto named_expr_n =
|
||||||
|
storage.Create<NamedExpression>("n", storage.Create<Identifier>("n"));
|
||||||
|
symbol_table[*named_expr_n] = symbol_table.CreateSymbol("named_expr_n");
|
||||||
|
symbol_table[*named_expr_n->expression_] = sym_n;
|
||||||
|
auto prop_lookup =
|
||||||
|
storage.Create<PropertyLookup>(storage.Create<Identifier>("n"), property);
|
||||||
|
symbol_table[*prop_lookup->expression_] = sym_n;
|
||||||
|
auto named_expr_n_p = storage.Create<NamedExpression>("n", prop_lookup);
|
||||||
|
symbol_table[*named_expr_n_p] = symbol_table.CreateSymbol("named_expr_n_p");
|
||||||
|
symbol_table[*named_expr_n->expression_] = sym_n;
|
||||||
|
|
||||||
|
auto produce = MakeProduce(create, named_expr_n, named_expr_n_p);
|
||||||
|
auto result = CollectProduce(produce, symbol_table, *dba);
|
||||||
|
EXPECT_EQ(1, result.GetResults().size());
|
||||||
|
EXPECT_EQ(2, result.GetResults()[0].size());
|
||||||
|
EXPECT_EQ(TypedValue::Type::Vertex, result.GetResults()[0][0].type());
|
||||||
|
EXPECT_EQ(1,
|
||||||
|
result.GetResults()[0][0].Value<VertexAccessor>().labels().size());
|
||||||
|
EXPECT_EQ(label,
|
||||||
|
result.GetResults()[0][0].Value<VertexAccessor>().labels()[0]);
|
||||||
|
EXPECT_EQ(TypedValue::Type::Int, result.GetResults()[0][1].type());
|
||||||
|
EXPECT_EQ(42, result.GetResults()[0][1].Value<int64_t>());
|
||||||
|
|
||||||
|
dba->advance_command();
|
||||||
|
EXPECT_EQ(1, CountIterable(dba->vertices()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Interpreter, CreateExpand) {
|
||||||
|
Dbms dbms;
|
||||||
|
auto dba = dbms.active();
|
||||||
|
|
||||||
|
GraphDb::Label label_node_1 = dba->label("Node1");
|
||||||
|
GraphDb::Label label_node_2 = dba->label("Node2");
|
||||||
|
GraphDb::Property property = dba->label("prop");
|
||||||
|
GraphDb::EdgeType edge_type = dba->label("edge_type");
|
||||||
|
|
||||||
|
SymbolTable symbol_table;
|
||||||
|
AstTreeStorage storage;
|
||||||
|
|
||||||
|
auto test_create_path = [&](bool cycle, int expected_nodes_created,
|
||||||
|
int expected_edges_created) {
|
||||||
|
int before_v = CountIterable(dba->vertices());
|
||||||
|
int before_e = CountIterable(dba->edges());
|
||||||
|
|
||||||
|
// data for the first node
|
||||||
|
auto n = storage.Create<NodeAtom>(storage.Create<Identifier>("n"));
|
||||||
|
n->labels_.emplace_back(label_node_1);
|
||||||
|
n->properties_[property] = storage.Create<Literal>(1);
|
||||||
|
auto n_sym = symbol_table.CreateSymbol("n");
|
||||||
|
symbol_table[*n->identifier_] = n_sym;
|
||||||
|
|
||||||
|
// data for the second node
|
||||||
|
auto m = storage.Create<NodeAtom>(storage.Create<Identifier>("m"));
|
||||||
|
m->labels_.emplace_back(label_node_2);
|
||||||
|
m->properties_[property] = storage.Create<Literal>(2);
|
||||||
|
if (cycle)
|
||||||
|
symbol_table[*m->identifier_] = n_sym;
|
||||||
|
else
|
||||||
|
symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m");
|
||||||
|
|
||||||
|
auto r = storage.Create<EdgeAtom>(storage.Create<Identifier>("r"),
|
||||||
|
EdgeAtom::Direction::RIGHT);
|
||||||
|
r->edge_types_.emplace_back(edge_type);
|
||||||
|
r->properties_[property] = storage.Create<Literal>(3);
|
||||||
|
|
||||||
|
auto create_op = std::make_shared<CreateOp>(n);
|
||||||
|
auto create_expand =
|
||||||
|
std::make_shared<CreateExpand>(m, r, create_op, n_sym, cycle);
|
||||||
|
ExecuteCreate(create_expand, *dba, symbol_table);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
|
EXPECT_EQ(CountIterable(dba->vertices()) - before_v,
|
||||||
|
expected_nodes_created);
|
||||||
|
EXPECT_EQ(CountIterable(dba->edges()) - before_e, expected_edges_created);
|
||||||
|
};
|
||||||
|
|
||||||
|
test_create_path(false, 2, 1);
|
||||||
|
test_create_path(true, 1, 1);
|
||||||
|
|
||||||
|
for (VertexAccessor vertex : dba->vertices()) {
|
||||||
|
EXPECT_EQ(vertex.labels().size(), 1);
|
||||||
|
GraphDb::Label label = vertex.labels()[0];
|
||||||
|
if (label == label_node_1) {
|
||||||
|
// node created by first op
|
||||||
|
EXPECT_EQ(vertex.PropsAt(property).Value<int64_t>(), 1);
|
||||||
|
} else if (label == label_node_2) {
|
||||||
|
// node create by expansion
|
||||||
|
EXPECT_EQ(vertex.PropsAt(property).Value<int64_t>(), 2);
|
||||||
|
} else {
|
||||||
|
// should not happen
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (EdgeAccessor edge : dba->edges()) {
|
||||||
|
EXPECT_EQ(edge.edge_type(), edge_type);
|
||||||
|
EXPECT_EQ(edge.PropsAt(property).Value<int64_t>(), 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Interpreter, MatchCreate) {
|
||||||
|
Dbms dbms;
|
||||||
|
auto dba = dbms.active();
|
||||||
|
|
||||||
|
// add three nodes we'll match and expand-create from
|
||||||
|
dba->insert_vertex();
|
||||||
|
dba->insert_vertex();
|
||||||
|
dba->insert_vertex();
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
|
// GraphDb::Label label_node_1 = dba->label("Node1");
|
||||||
|
// GraphDb::Label label_node_2 = dba->label("Node2");
|
||||||
|
// GraphDb::Property property = dba->label("prop");
|
||||||
|
GraphDb::EdgeType edge_type = dba->label("edge_type");
|
||||||
|
|
||||||
|
SymbolTable symbol_table;
|
||||||
|
AstTreeStorage storage;
|
||||||
|
|
||||||
|
auto test_create_path = [&](bool cycle, int expected_nodes_created,
|
||||||
|
int expected_edges_created) {
|
||||||
|
int before_v = CountIterable(dba->vertices());
|
||||||
|
int before_e = CountIterable(dba->edges());
|
||||||
|
|
||||||
|
// data for the first node
|
||||||
|
auto n_scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||||
|
auto n_sym = symbol_table.CreateSymbol("n");
|
||||||
|
symbol_table[*std::get<0>(n_scan_all)->identifier_] = n_sym;
|
||||||
|
|
||||||
|
// data for the second node
|
||||||
|
auto m = storage.Create<NodeAtom>(storage.Create<Identifier>("m"));
|
||||||
|
if (cycle)
|
||||||
|
symbol_table[*m->identifier_] = n_sym;
|
||||||
|
else
|
||||||
|
symbol_table[*m->identifier_] = symbol_table.CreateSymbol("m");
|
||||||
|
|
||||||
|
auto r = storage.Create<EdgeAtom>(storage.Create<Identifier>("r"),
|
||||||
|
EdgeAtom::Direction::RIGHT);
|
||||||
|
r->edge_types_.emplace_back(edge_type);
|
||||||
|
|
||||||
|
auto create_expand = std::make_shared<CreateExpand>(
|
||||||
|
m, r, std::get<1>(n_scan_all), n_sym, cycle);
|
||||||
|
ExecuteCreate(create_expand, *dba, symbol_table);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
|
EXPECT_EQ(CountIterable(dba->vertices()) - before_v,
|
||||||
|
expected_nodes_created);
|
||||||
|
EXPECT_EQ(CountIterable(dba->edges()) - before_e, expected_edges_created);
|
||||||
|
};
|
||||||
|
|
||||||
|
test_create_path(false, 3, 3);
|
||||||
|
test_create_path(true, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Interpreter, Expand) {
|
TEST(Interpreter, Expand) {
|
||||||
Dbms dbms;
|
Dbms dbms;
|
||||||
auto dba = dbms.active();
|
auto dba = dbms.active();
|
||||||
@ -279,11 +458,13 @@ TEST(Interpreter, Expand) {
|
|||||||
auto edge_type = dba->edge_type("Edge");
|
auto edge_type = dba->edge_type("Edge");
|
||||||
dba->insert_edge(v1, v2, edge_type);
|
dba->insert_edge(v1, v2, edge_type);
|
||||||
dba->insert_edge(v1, v3, edge_type);
|
dba->insert_edge(v1, v3, edge_type);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
|
|
||||||
auto test_expand = [&](EdgeAtom::Direction direction, int expected_result_count) {
|
auto test_expand = [&](EdgeAtom::Direction direction,
|
||||||
|
int expected_result_count) {
|
||||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||||
auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n),
|
auto r_m = MakeExpand(storage, symbol_table, std::get<1>(n), std::get<2>(n),
|
||||||
"r", direction, false, "m", false);
|
"r", direction, false, "m", false);
|
||||||
@ -315,6 +496,7 @@ TEST(Interpreter, ExpandNodeCycle) {
|
|||||||
auto edge_type = dba->edge_type("Edge");
|
auto edge_type = dba->edge_type("Edge");
|
||||||
dba->insert_edge(v1, v1, edge_type);
|
dba->insert_edge(v1, v1, edge_type);
|
||||||
dba->insert_edge(v1, v2, edge_type);
|
dba->insert_edge(v1, v2, edge_type);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
@ -357,6 +539,7 @@ TEST(Interpreter, ExpandEdgeCycle) {
|
|||||||
auto edge_type = dba->edge_type("Edge");
|
auto edge_type = dba->edge_type("Edge");
|
||||||
dba->insert_edge(v1, v2, edge_type);
|
dba->insert_edge(v1, v2, edge_type);
|
||||||
dba->insert_edge(v1, v3, edge_type);
|
dba->insert_edge(v1, v3, edge_type);
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
@ -417,6 +600,7 @@ TEST(Interpreter, EdgeFilter) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
|
@ -141,6 +141,7 @@ TEST(RecordAccessor, VertexEdgeConnections) {
|
|||||||
auto v1 = dba->insert_vertex();
|
auto v1 = dba->insert_vertex();
|
||||||
auto v2 = dba->insert_vertex();
|
auto v2 = dba->insert_vertex();
|
||||||
auto edge = dba->insert_edge(v1, v2, dba->edge_type("likes"));
|
auto edge = dba->insert_edge(v1, v2, dba->edge_type("likes"));
|
||||||
|
dba->advance_command();
|
||||||
|
|
||||||
EXPECT_EQ(edge.from(), v1);
|
EXPECT_EQ(edge.from(), v1);
|
||||||
EXPECT_NE(edge.from(), v2);
|
EXPECT_NE(edge.from(), v2);
|
||||||
|
Loading…
Reference in New Issue
Block a user