#include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "database/graph_db_accessor.hpp" #include "database/graph_db_datatypes.hpp" #include "dbms/dbms.hpp" #include "query/frontend/ast/ast.hpp" #include "query/frontend/opencypher/parser.hpp" #include "query/interpret/awesome_memgraph_functions.hpp" #include "query/interpret/eval.hpp" #include "query/interpret/frame.hpp" using namespace query; using testing::Pair; using testing::UnorderedElementsAre; using testing::ElementsAre; namespace { struct NoContextExpressionEvaluator { NoContextExpressionEvaluator() {} Frame frame{128}; SymbolTable symbol_table; Dbms dbms; std::unique_ptr dba = dbms.active(); ExpressionEvaluator eval{frame, symbol_table, *dba}; }; TypedValue EvaluateFunction(const std::string &function_name, const std::vector &args) { AstTreeStorage storage; NoContextExpressionEvaluator eval; Dbms dbms; auto dba = dbms.active(); std::vector expressions; for (const auto &arg : args) { expressions.push_back(storage.Create(arg)); } auto *op = storage.Create(NameToFunction(function_name), expressions); op->Accept(eval.eval); return eval.eval.PopBack(); } TEST(ExpressionEvaluator, OrOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(true), storage.Create(false)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(true), storage.Create(true)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, XorOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(true), storage.Create(false)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(true), storage.Create(true)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); } TEST(ExpressionEvaluator, AndOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(true), storage.Create(true)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(false), storage.Create(true)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); } TEST(ExpressionEvaluator, FilterAndOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; { auto *op = storage.Create( storage.Create(true), storage.Create(true)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), true); } { auto *op = storage.Create( storage.Create(false), storage.Create(5)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), false); } { auto *op = storage.Create( storage.Create(TypedValue::Null), storage.Create(5)); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } } TEST(ExpressionEvaluator, AdditionOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create( storage.Create(2), storage.Create(3)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), 5); } TEST(ExpressionEvaluator, SubtractionOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create( storage.Create(2), storage.Create(3)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), -1); } TEST(ExpressionEvaluator, MultiplicationOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create( storage.Create(2), storage.Create(3)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), 6); } TEST(ExpressionEvaluator, DivisionOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(50), storage.Create(10)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), 5); } TEST(ExpressionEvaluator, ModOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(65), storage.Create(10)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), 5); } TEST(ExpressionEvaluator, EqualOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create(storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); } TEST(ExpressionEvaluator, NotEqualOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create(storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, LessOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create(storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); } TEST(ExpressionEvaluator, GreaterOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create(storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create(storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, LessEqualOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create(storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); } TEST(ExpressionEvaluator, GreaterEqualOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create( storage.Create(10), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create( storage.Create(15), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); op = storage.Create( storage.Create(20), storage.Create(15)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, InListOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *list_literal = storage.Create(std::vector{ storage.Create(1), storage.Create(2), storage.Create("a")}); { // Element exists in list. auto *op = storage.Create( storage.Create(2), list_literal); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), true); } { // Element doesn't exist in list. auto *op = storage.Create( storage.Create("x"), list_literal); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), false); } { auto *list_literal = storage.Create(std::vector{ storage.Create(TypedValue::Null), storage.Create(2), storage.Create("a")}); // Element doesn't exist in list with null element. auto *op = storage.Create( storage.Create("x"), list_literal); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } { // Null list. auto *op = storage.Create( storage.Create("x"), storage.Create(TypedValue::Null)); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } } TEST(ExpressionEvaluator, ListIndexingOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *list_literal = storage.Create(std::vector{ storage.Create(1), storage.Create(2), storage.Create(3), storage.Create(4)}); { // Legal indexing. auto *op = storage.Create( list_literal, storage.Create(2)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), 3); } { // Out of bounds indexing. auto *op = storage.Create( list_literal, storage.Create(4)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null); } { // Out of bounds indexing with negative bound. auto *op = storage.Create( list_literal, storage.Create(-100)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null); } { // Legal indexing with negative index. auto *op = storage.Create( list_literal, storage.Create(-2)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), 3); } { // Indexing with one operator being null. auto *op = storage.Create( storage.Create(TypedValue::Null), storage.Create(-2)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null); } { // Indexing with incompatible type. auto *op = storage.Create( storage.Create(2), storage.Create(TypedValue::Null)); EXPECT_THROW(op->Accept(eval.eval), TypedValueException); } } TEST(ExpressionEvaluator, ListSlicingOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *list_literal = storage.Create(std::vector{ storage.Create(1), storage.Create(2), storage.Create(3), storage.Create(4)}); auto extract_ints = [](TypedValue list) { std::vector int_list; for (auto x : list.Value>()) { int_list.push_back(x.Value()); } return int_list; }; { // Legal slicing with both bounds defined. auto *op = storage.Create( list_literal, storage.Create(2), storage.Create(4)); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(3, 4)); } { // Legal slicing with negative bound. auto *op = storage.Create( list_literal, storage.Create(2), storage.Create(-1)); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(3)); } { // Lower bound larger than upper bound. auto *op = storage.Create( list_literal, storage.Create(2), storage.Create(-4)); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre()); } { // Bounds ouf or range. auto *op = storage.Create( list_literal, storage.Create(-100), storage.Create(10)); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(1, 2, 3, 4)); } { // Lower bound undefined. auto *op = storage.Create( list_literal, nullptr, storage.Create(3)); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(1, 2, 3)); } { // Upper bound undefined. auto *op = storage.Create( list_literal, storage.Create(-2), nullptr); op->Accept(eval.eval); EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(3, 4)); } { // Bound of illegal type and null value bound. auto *op = storage.Create( list_literal, storage.Create(TypedValue::Null), storage.Create("mirko")); EXPECT_THROW(op->Accept(eval.eval), TypedValueException); } { // List of illegal type. auto *op = storage.Create( storage.Create("a"), storage.Create(-2), nullptr); EXPECT_THROW(op->Accept(eval.eval), TypedValueException); } { // Null value list with undefined upper bound. auto *op = storage.Create( storage.Create(TypedValue::Null), storage.Create(-2), nullptr); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null); } { // Null value index. auto *op = storage.Create( list_literal, storage.Create(-2), storage.Create(TypedValue::Null)); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null); } } TEST(ExpressionEvaluator, NotOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(false)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, UnaryPlusOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(5)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), 5); } TEST(ExpressionEvaluator, UnaryMinusOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(5)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), -5); } TEST(ExpressionEvaluator, IsNullOperator) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *op = storage.Create(storage.Create(1)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), false); op = storage.Create( storage.Create(TypedValue::Null)); op->Accept(eval.eval); ASSERT_EQ(eval.eval.PopBack().Value(), true); } TEST(ExpressionEvaluator, PropertyLookup) { AstTreeStorage storage; NoContextExpressionEvaluator eval; Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.PropsSet(dba->property("age"), 10); auto *identifier = storage.Create("n"); auto node_symbol = eval.symbol_table.CreateSymbol("n"); eval.symbol_table[*identifier] = node_symbol; eval.frame[node_symbol] = v1; { auto *op = storage.Create(identifier, dba->property("age")); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), 10); } { auto *op = storage.Create(identifier, dba->property("height")); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } { eval.frame[node_symbol] = TypedValue::Null; auto *op = storage.Create(identifier, dba->property("age")); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } } TEST(ExpressionEvaluator, LabelsTest) { AstTreeStorage storage; NoContextExpressionEvaluator eval; Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.add_label(dba->label("ANIMAL")); v1.add_label(dba->label("DOG")); v1.add_label(dba->label("NICE_DOG")); auto *identifier = storage.Create("n"); auto node_symbol = eval.symbol_table.CreateSymbol("n"); eval.symbol_table[*identifier] = node_symbol; eval.frame[node_symbol] = v1; { auto *op = storage.Create( identifier, std::vector{dba->label("DOG"), dba->label("ANIMAL")}); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), true); } { auto *op = storage.Create( identifier, std::vector{ dba->label("DOG"), dba->label("BAD_DOG"), dba->label("ANIMAL")}); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), false); } { eval.frame[node_symbol] = TypedValue::Null; auto *op = storage.Create( identifier, std::vector{ dba->label("DOG"), dba->label("BAD_DOG"), dba->label("ANIMAL")}); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } } TEST(ExpressionEvaluator, EdgeTypeTest) { AstTreeStorage storage; NoContextExpressionEvaluator eval; Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); auto v2 = dba->insert_vertex(); auto e = dba->insert_edge(v1, v2, dba->edge_type("TYPE1")); auto *identifier = storage.Create("e"); auto edge_symbol = eval.symbol_table.CreateSymbol("e"); eval.symbol_table[*identifier] = edge_symbol; eval.frame[edge_symbol] = e; { auto *op = storage.Create( identifier, std::vector{ dba->edge_type("TYPE0"), dba->edge_type("TYPE1"), dba->edge_type("TYPE2")}); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), true); } { auto *op = storage.Create( identifier, std::vector{ dba->edge_type("TYPE0"), dba->edge_type("TYPE2")}); op->Accept(eval.eval); EXPECT_EQ(eval.eval.PopBack().Value(), false); } { eval.frame[edge_symbol] = TypedValue::Null; auto *op = storage.Create( identifier, std::vector{ dba->edge_type("TYPE0"), dba->edge_type("TYPE2")}); op->Accept(eval.eval); EXPECT_TRUE(eval.eval.PopBack().IsNull()); } } TEST(ExpressionEvaluator, Aggregation) { AstTreeStorage storage; auto aggr = storage.Create(storage.Create(42), Aggregation::Op::COUNT); SymbolTable symbol_table; auto aggr_sym = symbol_table.CreateSymbol("aggr"); symbol_table[*aggr] = aggr_sym; Frame frame{symbol_table.max_position()}; frame[aggr_sym] = TypedValue(1); Dbms dbms; auto dba = dbms.active(); ExpressionEvaluator eval{frame, symbol_table, *dba}; aggr->Accept(eval); EXPECT_EQ(eval.PopBack().Value(), 1); } TEST(ExpressionEvaluator, ListLiteral) { AstTreeStorage storage; NoContextExpressionEvaluator eval; auto *list_literal = storage.Create( std::vector{storage.Create(1), storage.Create("bla"), storage.Create(true)}); list_literal->Accept(eval.eval); TypedValue result = eval.eval.PopBack(); ASSERT_EQ(result.type(), TypedValue::Type::List); auto &result_elems = result.Value>(); ASSERT_EQ(3, result_elems.size()); EXPECT_EQ(result_elems[0].type(), TypedValue::Type::Int); EXPECT_EQ(result_elems[1].type(), TypedValue::Type::String); EXPECT_EQ(result_elems[2].type(), TypedValue::Type::Bool); } TEST(ExpressionEvaluator, FunctionCoalesce) { ASSERT_THROW(EvaluateFunction("COALESCE", {}), QueryRuntimeException); ASSERT_EQ( EvaluateFunction("COALESCE", {TypedValue::Null, TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ( EvaluateFunction("COALESCE", {TypedValue::Null, 2, 3}).Value(), 2); } TEST(ExpressionEvaluator, FunctionEndNode) { ASSERT_THROW(EvaluateFunction("ENDNODE", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("ENDNODE", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.add_label(dba->label("label1")); auto v2 = dba->insert_vertex(); v2.add_label(dba->label("label2")); auto e = dba->insert_edge(v1, v2, dba->edge_type("t")); ASSERT_TRUE(EvaluateFunction("ENDNODE", {e}) .Value() .has_label(dba->label("label2"))); ASSERT_THROW(EvaluateFunction("ENDNODE", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionHead) { ASSERT_THROW(EvaluateFunction("HEAD", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("HEAD", {TypedValue::Null}).type(), TypedValue::Type::Null); std::vector arguments; arguments.push_back(std::vector{3, 4, 5}); ASSERT_EQ(EvaluateFunction("HEAD", arguments).Value(), 3); arguments[0].Value>().clear(); ASSERT_EQ(EvaluateFunction("HEAD", arguments).type(), TypedValue::Type::Null); ASSERT_THROW(EvaluateFunction("HEAD", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionProperties) { ASSERT_THROW(EvaluateFunction("PROPERTIES", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("PROPERTIES", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.PropsSet(dba->property("height"), 5); v1.PropsSet(dba->property("age"), 10); auto v2 = dba->insert_vertex(); auto e = dba->insert_edge(v1, v2, dba->edge_type("type1")); e.PropsSet(dba->property("height"), 3); e.PropsSet(dba->property("age"), 15); auto prop_values_to_int = [](TypedValue t) { std::unordered_map properties; for (auto property : t.Value>()) { properties[property.first] = property.second.Value(); } return properties; }; ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {v1})), UnorderedElementsAre(Pair("height", 5), Pair("age", 10))); ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {e})), UnorderedElementsAre(Pair("height", 3), Pair("age", 15))); ASSERT_THROW(EvaluateFunction("PROPERTIES", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionLast) { ASSERT_THROW(EvaluateFunction("LAST", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("LAST", {TypedValue::Null}).type(), TypedValue::Type::Null); std::vector arguments; arguments.push_back(std::vector{3, 4, 5}); ASSERT_EQ(EvaluateFunction("LAST", arguments).Value(), 5); arguments[0].Value>().clear(); ASSERT_EQ(EvaluateFunction("LAST", arguments).type(), TypedValue::Type::Null); ASSERT_THROW(EvaluateFunction("LAST", {5}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionSize) { ASSERT_THROW(EvaluateFunction("SIZE", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("SIZE", {TypedValue::Null}).type(), TypedValue::Type::Null); std::vector arguments; arguments.push_back(std::vector{3, 4, 5}); ASSERT_EQ(EvaluateFunction("SIZE", arguments).Value(), 3); ASSERT_EQ(EvaluateFunction("SIZE", {"john"}).Value(), 4); ASSERT_EQ(EvaluateFunction("SIZE", {std::map{ {"a", 5}, {"b", true}, {"c", "123"}}}) .Value(), 3); ASSERT_THROW(EvaluateFunction("SIZE", {5}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionStartNode) { ASSERT_THROW(EvaluateFunction("STARTNODE", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("STARTNODE", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.add_label(dba->label("label1")); auto v2 = dba->insert_vertex(); v2.add_label(dba->label("label2")); auto e = dba->insert_edge(v1, v2, dba->edge_type("t")); ASSERT_TRUE(EvaluateFunction("STARTNODE", {e}) .Value() .has_label(dba->label("label1"))); ASSERT_THROW(EvaluateFunction("STARTNODE", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionToBoolean) { ASSERT_THROW(EvaluateFunction("TOBOOLEAN", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {" trUE \n\t"}).Value(), true); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFalsE "}).Value(), false); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFALSEA "}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {true}).Value(), true); ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {false}).Value(), false); ASSERT_THROW(EvaluateFunction("TOBOOLEAN", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionToFloat) { ASSERT_THROW(EvaluateFunction("TOFLOAT", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("TOFLOAT", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOFLOAT", {" -3.5 \n\t"}).Value(), -3.5); ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t0.5e-1"}).Value(), 0.05); ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t3.4e-3X "}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3.5}).Value(), -3.5); ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3}).Value(), -3.0); ASSERT_THROW(EvaluateFunction("TOFLOAT", {true}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionToInteger) { ASSERT_THROW(EvaluateFunction("TOINTEGER", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("TOINTEGER", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOINTEGER", {"\n\t3"}).Value(), 3); ASSERT_EQ(EvaluateFunction("TOINTEGER", {" -3.5 \n\t"}).Value(), -3); ASSERT_EQ(EvaluateFunction("TOINTEGER", {"\n\t3X "}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("TOINTEGER", {-3.5}).Value(), -3); ASSERT_EQ(EvaluateFunction("TOINTEGER", {3.5}).Value(), 3); ASSERT_THROW(EvaluateFunction("TOINTEGER", {true}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionType) { ASSERT_THROW(EvaluateFunction("TYPE", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("TYPE", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.add_label(dba->label("label1")); auto v2 = dba->insert_vertex(); v2.add_label(dba->label("label2")); auto e = dba->insert_edge(v1, v2, dba->edge_type("type1")); ASSERT_EQ(EvaluateFunction("TYPE", {e}).Value(), "type1"); ASSERT_THROW(EvaluateFunction("TYPE", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionLabels) { ASSERT_THROW(EvaluateFunction("LABELS", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("LABELS", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v = dba->insert_vertex(); v.add_label(dba->label("label1")); v.add_label(dba->label("label2")); std::vector labels; auto _labels = EvaluateFunction("LABELS", {v}).Value>(); for (auto label : _labels) { labels.push_back(label.Value()); } ASSERT_THAT(labels, UnorderedElementsAre("label1", "label2")); ASSERT_THROW(EvaluateFunction("LABELS", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionRange) { EXPECT_THROW(EvaluateFunction("RANGE", {}), QueryRuntimeException); EXPECT_TRUE(EvaluateFunction("RANGE", {1, 2, TypedValue::Null}).IsNull()); EXPECT_THROW(EvaluateFunction("RANGE", {1, TypedValue::Null, 1.3}), QueryRuntimeException); auto to_int_list = [](const TypedValue &t) { std::vector list; for (auto x : t.Value>()) { list.push_back(x.Value()); } return list; }; EXPECT_THROW(EvaluateFunction("RANGE", {1, 2, 0}), QueryRuntimeException); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {1, 3})), ElementsAre(1, 2, 3)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {-1, 5, 2})), ElementsAre(-1, 1, 3, 5)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {2, 10, 3})), ElementsAre(2, 5, 8)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {2, 2, 2})), ElementsAre(2)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {3, 0, 5})), ElementsAre()); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {5, 1, -2})), ElementsAre(5, 3, 1)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {6, 1, -2})), ElementsAre(6, 4, 2)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {2, 2, -3})), ElementsAre(2)); EXPECT_THAT(to_int_list(EvaluateFunction("RANGE", {-2, 4, -1})), ElementsAre()); } TEST(ExpressionEvaluator, FunctionKeys) { ASSERT_THROW(EvaluateFunction("KEYS", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("KEYS", {TypedValue::Null}).type(), TypedValue::Type::Null); Dbms dbms; auto dba = dbms.active(); auto v1 = dba->insert_vertex(); v1.PropsSet(dba->property("height"), 5); v1.PropsSet(dba->property("age"), 10); auto v2 = dba->insert_vertex(); auto e = dba->insert_edge(v1, v2, dba->edge_type("type1")); e.PropsSet(dba->property("width"), 3); e.PropsSet(dba->property("age"), 15); auto prop_keys_to_string = [](TypedValue t) { std::vector keys; for (auto property : t.Value>()) { keys.push_back(property.Value()); } return keys; }; ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", {v1})), UnorderedElementsAre("height", "age")); ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", {e})), UnorderedElementsAre("width", "age")); ASSERT_THROW(EvaluateFunction("KEYS", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionTail) { ASSERT_THROW(EvaluateFunction("TAIL", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("TAIL", {TypedValue::Null}).type(), TypedValue::Type::Null); std::vector arguments; arguments.push_back(std::vector{}); ASSERT_EQ(EvaluateFunction("TAIL", arguments) .Value>() .size(), 0U); arguments[0] = std::vector{3, 4, true, "john"}; auto list = EvaluateFunction("TAIL", arguments).Value>(); ASSERT_EQ(list.size(), 3U); ASSERT_EQ(list[0].Value(), 4); ASSERT_EQ(list[1].Value(), true); ASSERT_EQ(list[2].Value(), "john"); ASSERT_THROW(EvaluateFunction("TAIL", {2}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionAbs) { ASSERT_THROW(EvaluateFunction("ABS", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("ABS", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("ABS", {-2}).Value(), 2); ASSERT_EQ(EvaluateFunction("ABS", {-2.5}).Value(), 2.5); ASSERT_THROW(EvaluateFunction("ABS", {true}), QueryRuntimeException); } // Test if log works. If it does then all functions wrapped with // WRAP_CMATH_FLOAT_FUNCTION macro should work and are not gonna be tested for // correctnes.. TEST(ExpressionEvaluator, FunctionLog) { ASSERT_THROW(EvaluateFunction("LOG", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("LOG", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", {2}).Value(), log(2)); ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", {1.5}).Value(), log(1.5)); // Not portable, but should work on most platforms. ASSERT_TRUE(std::isnan(EvaluateFunction("LOG", {-1.5}).Value())); ASSERT_THROW(EvaluateFunction("LOG", {true}), QueryRuntimeException); } // Function Round wraps round from cmath and will work if FunctionLog test // passes. This test is used to show behavior of round since it differs from // neo4j's round. TEST(ExpressionEvaluator, FunctionRound) { ASSERT_THROW(EvaluateFunction("ROUND", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("ROUND", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("ROUND", {-2}).Value(), -2); ASSERT_EQ(EvaluateFunction("ROUND", {-2.4}).Value(), -2); ASSERT_EQ(EvaluateFunction("ROUND", {-2.5}).Value(), -3); ASSERT_EQ(EvaluateFunction("ROUND", {-2.6}).Value(), -3); ASSERT_EQ(EvaluateFunction("ROUND", {2.4}).Value(), 2); ASSERT_EQ(EvaluateFunction("ROUND", {2.5}).Value(), 3); ASSERT_EQ(EvaluateFunction("ROUND", {2.6}).Value(), 3); ASSERT_THROW(EvaluateFunction("ROUND", {true}), QueryRuntimeException); } // Check if wrapped functions are callable (check if everything was spelled // correctly...). Wrapper correctnes is checked in FunctionLog test. TEST(ExpressionEvaluator, FunctionWrappedMathFunctions) { for (auto function_name : {"FLOOR", "CEIL", "ROUND", "EXP", "LOG", "LOG10", "SQRT", "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN"}) { EvaluateFunction(function_name, {0.5}); } } TEST(ExpressionEvaluator, FunctionAtan2) { ASSERT_THROW(EvaluateFunction("ATAN2", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("ATAN2", {TypedValue::Null, 1}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("ATAN2", {1, TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_DOUBLE_EQ(EvaluateFunction("ATAN2", {2, -1.0}).Value(), atan2(2, -1)); ASSERT_THROW(EvaluateFunction("ATAN2", {3.0, true}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionSign) { ASSERT_THROW(EvaluateFunction("SIGN", {}), QueryRuntimeException); ASSERT_EQ(EvaluateFunction("SIGN", {TypedValue::Null}).type(), TypedValue::Type::Null); ASSERT_EQ(EvaluateFunction("SIGN", {-2}).Value(), -1); ASSERT_EQ(EvaluateFunction("SIGN", {-0.2}).Value(), -1); ASSERT_EQ(EvaluateFunction("SIGN", {0.0}).Value(), 0); ASSERT_EQ(EvaluateFunction("SIGN", {2.5}).Value(), 1); ASSERT_THROW(EvaluateFunction("SIGN", {true}), QueryRuntimeException); } TEST(ExpressionEvaluator, FunctionE) { ASSERT_THROW(EvaluateFunction("E", {1}), QueryRuntimeException); ASSERT_DOUBLE_EQ(EvaluateFunction("E", {}).Value(), M_E); } TEST(ExpressionEvaluator, FunctionPi) { ASSERT_THROW(EvaluateFunction("PI", {1}), QueryRuntimeException); ASSERT_DOUBLE_EQ(EvaluateFunction("PI", {}).Value(), M_PI); } }