From 1280e77fd3b9287e09875db6053dca810ab02870 Mon Sep 17 00:00:00 2001 From: florijan <florijan@memgraph.io> Date: Mon, 27 Mar 2017 13:09:14 +0200 Subject: [PATCH] Query - Logical - Delete op added and tested. Minor refactors. Reviewers: buda, teon.banek, mislav.bradac Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D184 --- src/query/exceptions.hpp | 9 + src/query/frontend/logical/operator.hpp | 92 ++++++- src/query/frontend/logical/planner.cpp | 10 +- src/query/frontend/logical/planner.hpp | 7 +- src/query/interpreter.hpp | 30 +- src/storage/util.hpp | 7 +- tests/unit/graph_db_accessor.cpp | 75 ++++- tests/unit/interpreter.cpp | 319 ++++++++-------------- tests/unit/query_common.hpp | 10 +- tests/unit/query_expression_evaluator.cpp | 234 ++++++++++++++++ tests/unit/query_planner.cpp | 7 +- 11 files changed, 538 insertions(+), 262 deletions(-) create mode 100644 tests/unit/query_expression_evaluator.cpp diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp index 872769ea2..ae6ebda9a 100644 --- a/src/query/exceptions.hpp +++ b/src/query/exceptions.hpp @@ -49,6 +49,15 @@ class TypeMismatchError : public SemanticException { name, datum, expected)) {} }; +/** + * An exception for an illegal operation that can not be detected + * before the query starts executing over data. + */ +class QueryRuntimeException : public BasicException { +public: + using BasicException::BasicException; +}; + class CppCodeGeneratorException : public StacktraceException { public: using StacktraceException::StacktraceException; diff --git a/src/query/frontend/logical/operator.hpp b/src/query/frontend/logical/operator.hpp index e9a3e6065..2a53c8099 100644 --- a/src/query/frontend/logical/operator.hpp +++ b/src/query/frontend/logical/operator.hpp @@ -1,6 +1,7 @@ #pragma once #include <memory> +#include <query/exceptions.hpp> #include <sstream> #include <vector> @@ -12,6 +13,7 @@ #include "utils/visitor/visitor.hpp" namespace query { +namespace plan { class Cursor { public: @@ -27,10 +29,11 @@ class NodeFilter; class EdgeFilter; class Filter; class Produce; +class Delete; using LogicalOperatorVisitor = ::utils::Visitor<CreateNode, CreateExpand, ScanAll, Expand, NodeFilter, - EdgeFilter, Filter, Produce>; + EdgeFilter, Filter, Produce, Delete>; class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> { public: @@ -55,7 +58,7 @@ class CreateNode : public LogicalOperator { * * @param node_atom * @param input Optional. If nullptr, then a single node will be - * created (a single successfull Pull from this Op's Cursor). + * created (a single successful Pull from this Op's Cursor). * If a valid input, then a node will be created for each * successful pull from the given input. */ @@ -687,10 +690,10 @@ class Produce : public LogicalOperator { class ProduceCursor : public Cursor { public: ProduceCursor(Produce &self, GraphDbAccessor &db) - : self_(self), self_cursor_(self_.input_->MakeCursor(db)) {} + : self_(self), input_cursor_(self_.input_->MakeCursor(db)) {} bool Pull(Frame &frame, SymbolTable &symbol_table) override { ExpressionEvaluator evaluator(frame, symbol_table); - if (self_cursor_->Pull(frame, symbol_table)) { + if (input_cursor_->Pull(frame, symbol_table)) { for (auto named_expr : self_.named_expressions_) { named_expr->Accept(evaluator); } @@ -701,11 +704,88 @@ class Produce : public LogicalOperator { private: Produce &self_; - std::unique_ptr<Cursor> self_cursor_; + std::unique_ptr<Cursor> input_cursor_; }; private: std::shared_ptr<LogicalOperator> input_; std::vector<NamedExpression *> named_expressions_; }; -} + +/** + * Operator for deleting vertices and edges. + * Has a flag for using DETACH DELETE when deleting + * vertices. + */ +class Delete : public LogicalOperator { + public: + Delete(const std::shared_ptr<LogicalOperator> &input_, + const std::vector<Expression *> &expressions, bool detach_) + : input_(input_), expressions_(expressions), detach_(detach_) {} + + void Accept(LogicalOperatorVisitor &visitor) override { + visitor.Visit(*this); + input_->Accept(visitor); + visitor.PostVisit(*this); + } + + private: + class DeleteCursor : public Cursor { + public: + DeleteCursor(Delete &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; + + ExpressionEvaluator evaluator(frame, symbol_table); + for (Expression *expression : self_.expressions_) { + expression->Accept(evaluator); + TypedValue value = evaluator.PopBack(); + switch (value.type()) { + case TypedValue::Type::Null: + // if we got a Null, that's OK, probably it's an OPTIONAL MATCH + return true; + case TypedValue::Type::Vertex: + if (self_.detach_) + db_.detach_remove_vertex(value.Value<VertexAccessor>()); + else if (!db_.remove_vertex(value.Value<VertexAccessor>())) + throw query::QueryRuntimeException( + "Failed to remove vertex because of it's existing " + "connections. Consider using DETACH DELETE."); + break; + case TypedValue::Type::Edge: + db_.remove_edge(value.Value<EdgeAccessor>()); + break; + case TypedValue::Type::Path: + // TODO consider path deletion + default: + throw TypedValueException("Can only delete edges and vertices"); + } + } + return true; + } + + private: + Delete &self_; + GraphDbAccessor &db_; + std::unique_ptr<Cursor> input_cursor_; + }; + + public: + std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) override { + return std::make_unique<DeleteCursor>(*this, db); + } + + private: + std::shared_ptr<LogicalOperator> input_; + std::vector<Expression *> expressions_; + // if the vertex should be detached before deletion + // if not detached, and has connections, an error is raised + // ignored when deleting edges + bool detach_; +}; +} // namespace plan +} // namespace query + + diff --git a/src/query/frontend/logical/planner.cpp b/src/query/frontend/logical/planner.cpp index d127bdfc3..98e87f994 100644 --- a/src/query/frontend/logical/planner.cpp +++ b/src/query/frontend/logical/planner.cpp @@ -6,6 +6,7 @@ #include "utils/exceptions/not_yet_implemented.hpp" namespace query { +namespace plan { namespace { @@ -58,7 +59,7 @@ auto ReducePattern( } auto GenCreateForPattern(Pattern &pattern, LogicalOperator *input_op, - const SymbolTable &symbol_table, + const query::SymbolTable &symbol_table, std::unordered_set<int> bound_symbols) { auto base = [&](NodeAtom *node) -> LogicalOperator * { if (BindSymbol(bound_symbols, symbol_table.at(*node->identifier_))) @@ -89,7 +90,7 @@ auto GenCreateForPattern(Pattern &pattern, LogicalOperator *input_op, } auto GenCreate(Create &create, LogicalOperator *input_op, - const SymbolTable &symbol_table, + const query::SymbolTable &symbol_table, std::unordered_set<int> bound_symbols) { auto last_op = input_op; for (auto pattern : create.patterns_) { @@ -100,7 +101,7 @@ auto GenCreate(Create &create, LogicalOperator *input_op, } auto GenMatch(Match &match, LogicalOperator *input_op, - const SymbolTable &symbol_table, + const query::SymbolTable &symbol_table, std::unordered_set<int> &bound_symbols) { auto base = [&](NodeAtom *node) { if (input_op) { @@ -166,7 +167,7 @@ auto GenReturn(Return &ret, LogicalOperator *input_op) { } // namespace std::unique_ptr<LogicalOperator> MakeLogicalPlan( - Query &query, const SymbolTable &symbol_table) { + query::Query &query, const query::SymbolTable &symbol_table) { // TODO: Extract functions and state into a class with methods. Possibly a // visitor or similar to avoid all those dynamic casts. LogicalOperator *input_op = nullptr; @@ -190,4 +191,5 @@ std::unique_ptr<LogicalOperator> MakeLogicalPlan( return std::unique_ptr<LogicalOperator>(input_op); } +} // namespace plan } // namespace query diff --git a/src/query/frontend/logical/planner.hpp b/src/query/frontend/logical/planner.hpp index 59c0af551..d784d204a 100644 --- a/src/query/frontend/logical/planner.hpp +++ b/src/query/frontend/logical/planner.hpp @@ -9,9 +9,12 @@ namespace query { class Query; class SymbolTable; +namespace plan { + // Returns the root of LogicalOperator tree. The tree is constructed by // traversing the given AST Query node. SymbolTable is used to determine inputs // and outputs of certain operators. -std::unique_ptr<LogicalOperator> -MakeLogicalPlan(Query &query, const SymbolTable &symbol_table); +std::unique_ptr<LogicalOperator> MakeLogicalPlan( + query::Query &query, const query::SymbolTable &symbol_table); +} } diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index 91c759a6a..f1060aaf4 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -15,7 +15,6 @@ namespace query { template <typename Stream> void Interpret(const std::string &query, GraphDbAccessor &db_accessor, Stream &stream) { - clock_t start_time = clock(); Config config; @@ -27,7 +26,7 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor, auto low_level_tree = parser.tree(); // AST -> high level tree - query::frontend::CypherMainVisitor visitor(ctx); + frontend::CypherMainVisitor visitor(ctx); visitor.visit(low_level_tree); auto high_level_tree = visitor.query(); @@ -37,12 +36,12 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor, high_level_tree->Accept(symbol_generator); // high level tree -> logical plan - auto logical_plan = MakeLogicalPlan(*high_level_tree, symbol_table); + auto logical_plan = plan::MakeLogicalPlan(*high_level_tree, symbol_table); // generate frame based on symbol table max_position Frame frame(symbol_table.max_position()); - if (auto produce = dynamic_cast<Produce *>(logical_plan.get())) { + if (auto produce = dynamic_cast<plan::Produce *>(logical_plan.get())) { // top level node in the operator tree is a produce (return) // so stream out results @@ -64,24 +63,23 @@ void Interpret(const std::string &query, GraphDbAccessor &db_accessor, for (auto &symbol : symbols) values.emplace_back(frame[symbol]); stream.Result(values); } - - summary["type"] = "r"; - } else if (auto create = dynamic_cast<CreateNode *>(logical_plan.get())) { - auto cursor = create->MakeCursor(db_accessor); - while (cursor->Pull(frame, symbol_table)) { + } else if (dynamic_cast<plan::CreateNode *>(logical_plan.get()) || + dynamic_cast<plan::CreateExpand *>(logical_plan.get()) || + dynamic_cast<Delete *>(logical_plan.get())) { + auto cursor = logical_plan.get()->MakeCursor(db_accessor); + while (cursor->Pull(frame, symbol_table)) continue; - } - } else if (auto create = dynamic_cast<CreateExpand *>(logical_plan.get())) { - auto cursor = create->MakeCursor(db_accessor); - while (cursor->Pull(frame, symbol_table)) { - continue; - } } clock_t end_time = clock(); double time_second = double(end_time - start_time) / CLOCKS_PER_SEC; summary["query_time_sec"] = TypedValue(time_second); + // TODO set summary['type'] based on transaction metadata + // the type can't be determined based only on top level LogicalOp + // (for example MATCH DELETE RETURN will have Produce as it's top) + // for now always se "rw" because something must be set, but it doesn't + // have to be correct (for Bolt clients) + summary["type"] = "rw"; stream.Summary(summary); - } } diff --git a/src/storage/util.hpp b/src/storage/util.hpp index bf54db26b..306b7d1c4 100644 --- a/src/storage/util.hpp +++ b/src/storage/util.hpp @@ -1,5 +1,6 @@ #pragma once +#include <cppitertools/reversed.hpp> #include "cppitertools/imap.hpp" /** @@ -16,5 +17,9 @@ template <typename TAccessor, typename TIterable> auto make_accessor_iterator(const TIterable &records, GraphDbAccessor &db_accessor) { return iter::imap([&db_accessor](auto vlist) { return TAccessor(*vlist, db_accessor); - }, records); + // note that here we iterate over records in REVERSED order + // this is necessary for DETACH DELETE (see GraphDbAccessor) + // which deletes items from relationship collections in a + // vertex accessor + }, iter::reversed(records)); } diff --git a/tests/unit/graph_db_accessor.cpp b/tests/unit/graph_db_accessor.cpp index 7a1c9ef65..badbdb414 100644 --- a/tests/unit/graph_db_accessor.cpp +++ b/tests/unit/graph_db_accessor.cpp @@ -162,52 +162,98 @@ TEST(GraphDbAccessorTest, DetachRemoveVertex) { Dbms dbms; auto dba = dbms.active(); - // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) + // setup (v1)- []->(v2)<-[]-(v3)<-[]-(v4) auto va1 = dba->insert_vertex(); auto va2 = dba->insert_vertex(); auto va3 = dba->insert_vertex(); - dba->insert_edge(va1, va2, dba->edge_type("likes")); - dba->insert_edge(va1, va3, dba->edge_type("likes")); + auto va4 = dba->insert_vertex(); + auto edge_type = dba->edge_type("type"); + dba->insert_edge(va1, va2, edge_type); + dba->insert_edge(va1, va3, edge_type); + dba->insert_edge(va4, va3, edge_type); dba->advance_command(); // ensure that plain remove does NOT work - EXPECT_EQ(CountVertices(*dba), 3); - EXPECT_EQ(CountEdges(*dba), 2); + EXPECT_EQ(CountVertices(*dba), 4); + EXPECT_EQ(CountEdges(*dba), 3); EXPECT_FALSE(dba->remove_vertex(va1)); EXPECT_FALSE(dba->remove_vertex(va2)); EXPECT_FALSE(dba->remove_vertex(va3)); - EXPECT_EQ(CountVertices(*dba), 3); - EXPECT_EQ(CountEdges(*dba), 2); + EXPECT_EQ(CountVertices(*dba), 4); + EXPECT_EQ(CountEdges(*dba), 3); - // make a new transaction because at the moment deletions - // in the same transaction are not visible - // DETACH REMOVE V3 - // new situation: (v1) - [:likes] -> (v2) dba->detach_remove_vertex(va3); dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 3); + EXPECT_EQ(CountEdges(*dba), 1); + EXPECT_TRUE(dba->remove_vertex(va4)); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 2); EXPECT_EQ(CountEdges(*dba), 1); for (auto va : dba->vertices()) EXPECT_FALSE(dba->remove_vertex(va)); - dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 2); EXPECT_EQ(CountEdges(*dba), 1); - for (auto va : dba->vertices()) { EXPECT_FALSE(dba->remove_vertex(va)); dba->detach_remove_vertex(va); break; } - dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 1); EXPECT_EQ(CountEdges(*dba), 0); // remove the last vertex, it has no connections // so that should work for (auto va : dba->vertices()) EXPECT_TRUE(dba->remove_vertex(va)); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), 0); + EXPECT_EQ(CountEdges(*dba), 0); +} + +TEST(GraphDbAccessorTest, DetachRemoveVertexMultiple) { + // This test checks that we can detach remove the + // same vertex / edge multiple times + + Dbms dbms; + auto dba = dbms.active(); + + // setup: make a fully connected N graph + // with cycles too! + int N = 7; + std::vector<VertexAccessor> vertices; + auto edge_type = dba->edge_type("edge"); + for (int i = 0; i < N; ++i) + vertices.emplace_back(dba->insert_vertex()); + for (int j = 0; j < N; ++j) + for (int k = 0; k < N; ++k) + dba->insert_edge(vertices[j], vertices[k], edge_type); + dba->advance_command(); + + EXPECT_EQ(CountVertices(*dba), N); + EXPECT_EQ(CountEdges(*dba), N * N); + + // detach delete one edge + dba->detach_remove_vertex(vertices[0]); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), N - 1); + EXPECT_EQ(CountEdges(*dba), (N - 1) * (N - 1)); + + // detach delete two neighboring edges + dba->detach_remove_vertex(vertices[1]); + dba->detach_remove_vertex(vertices[2]); + dba->advance_command(); + EXPECT_EQ(CountVertices(*dba), N - 3); + EXPECT_EQ(CountEdges(*dba), (N - 3) * (N - 3)); + + // detach delete everything, buwahahahaha + for (int l = 3; l < N ; ++l) + dba->detach_remove_vertex(vertices[l]); dba->advance_command(); EXPECT_EQ(CountVertices(*dba), 0); EXPECT_EQ(CountEdges(*dba), 0); @@ -257,5 +303,6 @@ TEST(GraphDbAccessorTest, Properties) { int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); +// ::testing::GTEST_FLAG(filter) = "*.DetachRemoveVertex"; return RUN_ALL_TESTS(); } diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index c71184ec4..54f4644c6 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -15,12 +15,11 @@ #include "query/context.hpp" #include "query/frontend/interpret/interpret.hpp" #include "query/frontend/logical/planner.hpp" -#include "query/frontend/opencypher/parser.hpp" -#include "query/frontend/semantic/symbol_generator.hpp" #include "query_common.hpp" using namespace query; +using namespace query::plan; /** * Helper function that collects all the results from the given @@ -63,10 +62,10 @@ auto CollectProduce(std::shared_ptr<Produce> produce, SymbolTable &symbol_table, return stream; } -void ExecuteCreate(std::shared_ptr<LogicalOperator> create, GraphDbAccessor &db, - SymbolTable symbol_table) { +void PullAll(std::shared_ptr<LogicalOperator> logical_op, GraphDbAccessor &db, + SymbolTable symbol_table) { Frame frame(symbol_table.max_position()); - auto cursor = create->MakeCursor(db); + auto cursor = logical_op->MakeCursor(db); while (cursor->Pull(frame, symbol_table)) { continue; } @@ -256,7 +255,7 @@ TEST(Interpreter, CreateNodeWithAttributes) { node->properties_[property] = LITERAL(42); auto create = std::make_shared<CreateNode>(node, nullptr); - ExecuteCreate(create, *dba, symbol_table); + PullAll(create, *dba, symbol_table); dba->advance_command(); // count the number of vertices @@ -356,7 +355,7 @@ TEST(Interpreter, CreateExpand) { auto create_op = std::make_shared<CreateNode>(n, nullptr); auto create_expand = std::make_shared<CreateExpand>(m, r, create_op, n_sym, cycle); - ExecuteCreate(create_expand, *dba, symbol_table); + PullAll(create_expand, *dba, symbol_table); dba->advance_command(); EXPECT_EQ(CountIterable(dba->vertices()) - before_v, @@ -412,7 +411,7 @@ TEST(Interpreter, MatchCreateNode) { auto create_node = std::make_shared<CreateNode>(m, std::get<1>(n_scan_all)); EXPECT_EQ(CountIterable(dba->vertices()), 3); - ExecuteCreate(create_node, *dba, symbol_table); + PullAll(create_node, *dba, symbol_table); dba->advance_command(); EXPECT_EQ(CountIterable(dba->vertices()), 6); } @@ -457,7 +456,7 @@ TEST(Interpreter, MatchCreateExpand) { auto create_expand = std::make_shared<CreateExpand>( m, r, std::get<1>(n_scan_all), n_sym, cycle); - ExecuteCreate(create_expand, *dba, symbol_table); + PullAll(create_expand, *dba, symbol_table); dba->advance_command(); EXPECT_EQ(CountIterable(dba->vertices()) - before_v, @@ -687,221 +686,117 @@ TEST(Interpreter, EdgeFilterMultipleTypes) { EXPECT_EQ(result.GetResults().size(), 2); } -struct NoContextExpressionEvaluator { - NoContextExpressionEvaluator() {} - Frame frame{0}; +TEST(Interpreter, Delete) { + Dbms dbms; + auto dba = dbms.active(); + + // make a fully-connected (one-direction, no cycles) with 4 nodes + std::vector<VertexAccessor> vertices; + for (int i = 0; i < 4; ++i) vertices.push_back(dba->insert_vertex()); + auto type = dba->edge_type("type"); + for (int j = 0; j < 4; ++j) + for (int k = j + 1; k < 4; ++k) + dba->insert_edge(vertices[j], vertices[k], type); + + dba->advance_command(); + EXPECT_EQ(4, CountIterable(dba->vertices())); + EXPECT_EQ(6, CountIterable(dba->edges())); + + AstTreeStorage storage; SymbolTable symbol_table; - ExpressionEvaluator eval{frame, symbol_table}; -}; -TEST(ExpressionEvaluator, OrOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<OrOperator>(storage.Create<Literal>(true), - storage.Create<Literal>(false)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<OrOperator>(storage.Create<Literal>(true), - storage.Create<Literal>(true)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + // attempt to delete a vertex, and fail + { + auto n = MakeScanAll(storage, symbol_table, "n"); + auto n_get = storage.Create<Identifier>("n"); + symbol_table[*n_get] = std::get<2>(n); + auto delete_op = std::make_shared<plan::Delete>( + std::get<1>(n), std::vector<Expression *>{n_get}, false); + EXPECT_THROW(PullAll(delete_op, *dba, symbol_table), QueryRuntimeException); + dba->advance_command(); + EXPECT_EQ(4, CountIterable(dba->vertices())); + EXPECT_EQ(6, CountIterable(dba->edges())); + } + + // detach delete a single vertex + { + auto n = MakeScanAll(storage, symbol_table, "n"); + auto n_get = storage.Create<Identifier>("n"); + symbol_table[*n_get] = std::get<2>(n); + auto delete_op = std::make_shared<plan::Delete>( + std::get<1>(n), std::vector<Expression *>{n_get}, true); + Frame frame(symbol_table.max_position()); + delete_op->MakeCursor(*dba)->Pull(frame, symbol_table); + dba->advance_command(); + EXPECT_EQ(3, CountIterable(dba->vertices())); + EXPECT_EQ(3, CountIterable(dba->edges())); + } + + // delete all remaining edges + { + 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); + auto r_get = storage.Create<Identifier>("r"); + symbol_table[*r_get] = std::get<1>(r_m); + auto delete_op = std::make_shared<plan::Delete>( + std::get<4>(r_m), std::vector<Expression *>{r_get}, false); + PullAll(delete_op, *dba, symbol_table); + dba->advance_command(); + EXPECT_EQ(3, CountIterable(dba->vertices())); + EXPECT_EQ(0, CountIterable(dba->edges())); + } + + // delete all remaining vertices + { + auto n = MakeScanAll(storage, symbol_table, "n"); + auto n_get = storage.Create<Identifier>("n"); + symbol_table[*n_get] = std::get<2>(n); + auto delete_op = std::make_shared<plan::Delete>( + std::get<1>(n), std::vector<Expression *>{n_get}, false); + PullAll(delete_op, *dba, symbol_table); + dba->advance_command(); + EXPECT_EQ(0, CountIterable(dba->vertices())); + EXPECT_EQ(0, CountIterable(dba->edges())); + } } -TEST(ExpressionEvaluator, XorOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<XorOperator>(storage.Create<Literal>(true), - storage.Create<Literal>(false)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<XorOperator>(storage.Create<Literal>(true), - storage.Create<Literal>(true)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); -} +TEST(Interpreter, DeleteReturn) { + Dbms dbms; + auto dba = dbms.active(); -TEST(ExpressionEvaluator, AndOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<AndOperator>(storage.Create<Literal>(true), - storage.Create<Literal>(true)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<AndOperator>(storage.Create<Literal>(false), - storage.Create<Literal>(true)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); -} + // make a fully-connected (one-direction, no cycles) with 4 nodes + auto prop = dba->property("prop"); + for (int i = 0; i < 4; ++i) { + auto va = dba->insert_vertex(); + va.PropsSet(prop, 42); + } -TEST(ExpressionEvaluator, AdditionOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<AdditionOperator>(storage.Create<Literal>(2), - storage.Create<Literal>(3)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); -} + dba->advance_command(); + EXPECT_EQ(4, CountIterable(dba->vertices())); + EXPECT_EQ(0, CountIterable(dba->edges())); -TEST(ExpressionEvaluator, SubtractionOperator) { AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<SubtractionOperator>(storage.Create<Literal>(2), - storage.Create<Literal>(3)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -1); -} + SymbolTable symbol_table; -TEST(ExpressionEvaluator, MultiplicationOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<MultiplicationOperator>(storage.Create<Literal>(2), - storage.Create<Literal>(3)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 6); -} + auto n = MakeScanAll(storage, symbol_table, "n"); -TEST(ExpressionEvaluator, DivisionOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<DivisionOperator>(storage.Create<Literal>(50), - storage.Create<Literal>(10)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); -} + auto n_get = storage.Create<Identifier>("n"); + symbol_table[*n_get] = std::get<2>(n); + auto delete_op = std::make_shared<plan::Delete>( + std::get<1>(n), std::vector<Expression *>{n_get}, true); -TEST(ExpressionEvaluator, ModOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<ModOperator>(storage.Create<Literal>(65), - storage.Create<Literal>(10)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); -} + auto prop_lookup = + storage.Create<PropertyLookup>(storage.Create<Identifier>("n"), prop); + symbol_table[*prop_lookup->expression_] = std::get<2>(n); + auto n_p = storage.Create<NamedExpression>("n", prop_lookup); + symbol_table[*n_p] = symbol_table.CreateSymbol("bla"); + auto produce = MakeProduce(delete_op, n_p); -TEST(ExpressionEvaluator, EqualOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<EqualOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<EqualOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<EqualOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); -} - -TEST(ExpressionEvaluator, NotEqualOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<NotEqualOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<NotEqualOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<NotEqualOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); -} - -TEST(ExpressionEvaluator, LessOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<LessOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<LessOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<LessOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); -} - -TEST(ExpressionEvaluator, GreaterOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<GreaterOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<GreaterOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<GreaterOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); -} - -TEST(ExpressionEvaluator, LessEqualOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<LessEqualOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<LessEqualOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<LessEqualOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); -} - -TEST(ExpressionEvaluator, GreaterEqualOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(10), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); - op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(15), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); - op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(20), - storage.Create<Literal>(15)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); -} - -TEST(ExpressionEvaluator, NotOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<NotOperator>(storage.Create<Literal>(false)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); -} - -TEST(ExpressionEvaluator, UnaryPlusOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<UnaryPlusOperator>(storage.Create<Literal>(5)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); -} - -TEST(ExpressionEvaluator, UnaryMinusOperator) { - AstTreeStorage storage; - NoContextExpressionEvaluator eval; - auto *op = storage.Create<UnaryMinusOperator>(storage.Create<Literal>(5)); - op->Accept(eval.eval); - ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -5); + auto result = CollectProduce(produce, symbol_table, *dba); + EXPECT_EQ(4, result.GetResults().size()); + dba->advance_command(); + EXPECT_EQ(0, CountIterable(dba->vertices())); } TEST(Interpreter, Filter) { diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index e5ce3fb8b..be350b52c 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -111,12 +111,12 @@ auto GetReturn(AstTreeStorage &storage, #define EDGE(...) query::test_common::GetEdge(storage, __VA_ARGS__) #define PATTERN(...) query::test_common::GetPattern(storage, {__VA_ARGS__}) #define MATCH(...) \ - query::test_common::GetWithPatterns<Match>(storage, {__VA_ARGS__}) + query::test_common::GetWithPatterns<query::Match>(storage, {__VA_ARGS__}) #define CREATE(...) \ - query::test_common::GetWithPatterns<Create>(storage, {__VA_ARGS__}) -#define IDENT(name) storage.Create<Identifier>((name)) -#define LITERAL(val) storage.Create<Literal>((val)) + query::test_common::GetWithPatterns<query::Create>(storage, {__VA_ARGS__}) +#define IDENT(name) storage.Create<query::Identifier>((name)) +#define LITERAL(val) storage.Create<query::Literal>((val)) #define PROPERTY_LOOKUP(...) query::test_common::GetPropertyLookup(storage, __VA_ARGS__) -#define NEXPR(name, expr) storage.Create<NamedExpression>((name), (expr)) +#define NEXPR(name, expr) storage.Create<query::NamedExpression>((name), (expr)) #define RETURN(...) query::test_common::GetReturn(storage, {__VA_ARGS__}) #define QUERY(...) query::test_common::GetQuery(storage, {__VA_ARGS__}) diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp new file mode 100644 index 000000000..4d9fee0b3 --- /dev/null +++ b/tests/unit/query_expression_evaluator.cpp @@ -0,0 +1,234 @@ +// +// Copyright 2017 Memgraph +// Created by Mislav Bradac on 27.03.17. +// + +#include <iterator> +#include <memory> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "query/frontend/ast/ast.hpp" +#include "query/frontend/interpret/interpret.hpp" +#include "query/frontend/opencypher/parser.hpp" + +using namespace query; + +struct NoContextExpressionEvaluator { + NoContextExpressionEvaluator() {} + Frame frame{0}; + SymbolTable symbol_table; + ExpressionEvaluator eval{frame, symbol_table}; +}; + +TEST(ExpressionEvaluator, OrOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<OrOperator>(storage.Create<Literal>(true), + storage.Create<Literal>(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<OrOperator>(storage.Create<Literal>(true), + storage.Create<Literal>(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); +} + +TEST(ExpressionEvaluator, XorOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<XorOperator>(storage.Create<Literal>(true), + storage.Create<Literal>(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<XorOperator>(storage.Create<Literal>(true), + storage.Create<Literal>(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); +} + +TEST(ExpressionEvaluator, AndOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<AndOperator>(storage.Create<Literal>(true), + storage.Create<Literal>(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<AndOperator>(storage.Create<Literal>(false), + storage.Create<Literal>(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); +} + +TEST(ExpressionEvaluator, AdditionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<AdditionOperator>(storage.Create<Literal>(2), + storage.Create<Literal>(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); +} + +TEST(ExpressionEvaluator, SubtractionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<SubtractionOperator>(storage.Create<Literal>(2), + storage.Create<Literal>(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -1); +} + +TEST(ExpressionEvaluator, MultiplicationOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<MultiplicationOperator>(storage.Create<Literal>(2), + storage.Create<Literal>(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 6); +} + +TEST(ExpressionEvaluator, DivisionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<DivisionOperator>(storage.Create<Literal>(50), + storage.Create<Literal>(10)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); +} + +TEST(ExpressionEvaluator, ModOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<ModOperator>(storage.Create<Literal>(65), + storage.Create<Literal>(10)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); +} + +TEST(ExpressionEvaluator, EqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<EqualOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<EqualOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<EqualOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); +} + +TEST(ExpressionEvaluator, NotEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<NotEqualOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<NotEqualOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<NotEqualOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); +} + +TEST(ExpressionEvaluator, LessOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<LessOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<LessOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<LessOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); +} + +TEST(ExpressionEvaluator, GreaterOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<GreaterOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<GreaterOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<GreaterOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); +} + +TEST(ExpressionEvaluator, LessEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<LessEqualOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<LessEqualOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<LessEqualOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); +} + +TEST(ExpressionEvaluator, GreaterEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(10), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false); + op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(15), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); + op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(20), + storage.Create<Literal>(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); +} + +TEST(ExpressionEvaluator, NotOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<NotOperator>(storage.Create<Literal>(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true); +} + +TEST(ExpressionEvaluator, UnaryPlusOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<UnaryPlusOperator>(storage.Create<Literal>(5)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5); +} + +TEST(ExpressionEvaluator, UnaryMinusOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create<UnaryMinusOperator>(storage.Create<Literal>(5)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -5); +} diff --git a/tests/unit/query_planner.cpp b/tests/unit/query_planner.cpp index 0a3d03047..e277d970d 100644 --- a/tests/unit/query_planner.cpp +++ b/tests/unit/query_planner.cpp @@ -11,8 +11,11 @@ #include "query_common.hpp" -using namespace query; -using Direction = EdgeAtom::Direction; +using namespace query::plan; +using query::AstTreeStorage; +using query::SymbolTable; +using query::SymbolGenerator; +using Direction = query::EdgeAtom::Direction; namespace {