03c78c1277
Summary: - The new BFS syntax implemented as proposed. - AST BreadthFirstAtom now uses EdgeAtom members: has_range_{true}, upper_bound_, lower_bound_ - Edges data structure now handles all the edge filtering (single or multiple edges), to ease planning. Additional edge filtering (additional Filter op in the plan) is removed. AST EdgeTypeTest is no longer used and is removed. Current state is stable but there are things left to do: - BFS property filtering. - BFS lower_bound_ support. - Support for lambdas in variable length expansion. This includes obligatory (even if not user_defined) inner_node and inner_edge symbols for easier handling. - Code-sharing between BFS and variable length expansions. I'll add asana tasks (and probably start working on them immediately) when/if this lands. Reviewers: buda, teon.banek, mislav.bradac Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D836
1224 lines
48 KiB
C++
1224 lines
48 KiB
C++
#include <cmath>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "database/dbms.hpp"
|
|
#include "database/graph_db_accessor.hpp"
|
|
#include "database/graph_db_datatypes.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"
|
|
#include "utils/string.hpp"
|
|
|
|
#include "query_common.hpp"
|
|
|
|
using namespace query;
|
|
using testing::UnorderedElementsAre;
|
|
using testing::ElementsAre;
|
|
using query::test_common::ToList;
|
|
|
|
namespace {
|
|
|
|
struct NoContextExpressionEvaluator {
|
|
NoContextExpressionEvaluator() {}
|
|
Frame frame{128};
|
|
SymbolTable symbol_table;
|
|
Dbms dbms;
|
|
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
|
|
Parameters parameters;
|
|
ExpressionEvaluator eval{frame, parameters, symbol_table, *dba};
|
|
};
|
|
|
|
TypedValue EvaluateFunction(const std::string &function_name,
|
|
const std::vector<TypedValue> &args, Dbms &dbms) {
|
|
AstTreeStorage storage;
|
|
SymbolTable symbol_table;
|
|
auto dba = dbms.active();
|
|
Frame frame{128};
|
|
Parameters parameters;
|
|
ExpressionEvaluator eval{frame, parameters, symbol_table, *dba};
|
|
|
|
std::vector<Expression *> expressions;
|
|
for (const auto &arg : args) {
|
|
expressions.push_back(storage.Create<PrimitiveLiteral>(arg));
|
|
}
|
|
auto *op =
|
|
storage.Create<Function>(NameToFunction(function_name), expressions);
|
|
return op->Accept(eval);
|
|
}
|
|
|
|
TypedValue EvaluateFunction(const std::string &function_name,
|
|
const std::vector<TypedValue> &args) {
|
|
Dbms dbms;
|
|
return EvaluateFunction(function_name, args, dbms);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, OrOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true),
|
|
storage.Create<PrimitiveLiteral>(false));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true),
|
|
storage.Create<PrimitiveLiteral>(true));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), true);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, XorOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true),
|
|
storage.Create<PrimitiveLiteral>(false));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true),
|
|
storage.Create<PrimitiveLiteral>(true));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), false);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, AndOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(true),
|
|
storage.Create<PrimitiveLiteral>(true));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false),
|
|
storage.Create<PrimitiveLiteral>(true));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), false);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, AndOperatorShortCircuit) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
{
|
|
auto *op =
|
|
storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false),
|
|
storage.Create<PrimitiveLiteral>(5));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<bool>(), false);
|
|
}
|
|
{
|
|
auto *op = storage.Create<AndOperator>(
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
|
storage.Create<PrimitiveLiteral>(5));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, AdditionOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<AdditionOperator>(
|
|
storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 5);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, SubtractionOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<SubtractionOperator>(
|
|
storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), -1);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, MultiplicationOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<MultiplicationOperator>(
|
|
storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 6);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, DivisionOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<DivisionOperator>(storage.Create<PrimitiveLiteral>(50),
|
|
storage.Create<PrimitiveLiteral>(10));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 5);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, ModOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<ModOperator>(storage.Create<PrimitiveLiteral>(65),
|
|
storage.Create<PrimitiveLiteral>(10));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 5);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, EqualOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), false);
|
|
op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), true);
|
|
op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), false);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, NotEqualOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), false);
|
|
op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), true);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, LessOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), false);
|
|
op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), false);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, GreaterOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), false);
|
|
op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), false);
|
|
op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), true);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, LessEqualOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), true);
|
|
op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), true);
|
|
op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), false);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, GreaterEqualOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op = storage.Create<GreaterEqualOperator>(
|
|
storage.Create<PrimitiveLiteral>(10),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), false);
|
|
op = storage.Create<GreaterEqualOperator>(
|
|
storage.Create<PrimitiveLiteral>(15),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), true);
|
|
op = storage.Create<GreaterEqualOperator>(
|
|
storage.Create<PrimitiveLiteral>(20),
|
|
storage.Create<PrimitiveLiteral>(15));
|
|
auto val3 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val3.Value<bool>(), true);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, InListOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
|
|
storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>("a")});
|
|
{
|
|
// Element exists in list.
|
|
auto *op = storage.Create<InListOperator>(
|
|
storage.Create<PrimitiveLiteral>(2), list_literal);
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<bool>(), true);
|
|
}
|
|
{
|
|
// Element doesn't exist in list.
|
|
auto *op = storage.Create<InListOperator>(
|
|
storage.Create<PrimitiveLiteral>("x"), list_literal);
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<bool>(), false);
|
|
}
|
|
{
|
|
auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
|
storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>("a")});
|
|
// Element doesn't exist in list with null element.
|
|
auto *op = storage.Create<InListOperator>(
|
|
storage.Create<PrimitiveLiteral>("x"), list_literal);
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
{
|
|
// Null list.
|
|
auto *op = storage.Create<InListOperator>(
|
|
storage.Create<PrimitiveLiteral>("x"),
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, ListMapIndexingOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
|
|
storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(3),
|
|
storage.Create<PrimitiveLiteral>(4)});
|
|
{
|
|
// Legal indexing.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(2));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<int64_t>(), 3);
|
|
}
|
|
{
|
|
// Out of bounds indexing.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(4));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.type(), TypedValue::Type::Null);
|
|
}
|
|
{
|
|
// Out of bounds indexing with negative bound.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(-100));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.type(), TypedValue::Type::Null);
|
|
}
|
|
{
|
|
// Legal indexing with negative index.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(-2));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<int64_t>(), 3);
|
|
}
|
|
{
|
|
// Indexing with one operator being null.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
|
storage.Create<PrimitiveLiteral>(-2));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.type(), TypedValue::Type::Null);
|
|
}
|
|
{
|
|
// Indexing with incompatible type.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null));
|
|
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, MapIndexing) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
Dbms dbms;
|
|
auto dba = dbms.active();
|
|
auto *map_literal = storage.Create<MapLiteral>(
|
|
std::map<std::pair<std::string, GraphDbTypes::Property>, Expression *>{
|
|
{PROPERTY_PAIR("a"), storage.Create<PrimitiveLiteral>(1)},
|
|
{PROPERTY_PAIR("b"), storage.Create<PrimitiveLiteral>(2)},
|
|
{PROPERTY_PAIR("c"), storage.Create<PrimitiveLiteral>(3)}});
|
|
{
|
|
// Legal indexing.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
map_literal, storage.Create<PrimitiveLiteral>("b"));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<int64_t>(), 2);
|
|
}
|
|
{
|
|
// Legal indexing, non-existing key.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
map_literal, storage.Create<PrimitiveLiteral>("z"));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
{
|
|
// Wrong key type.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
map_literal, storage.Create<PrimitiveLiteral>(42));
|
|
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
{
|
|
// Indexing with Null.
|
|
auto *op = storage.Create<ListMapIndexingOperator>(
|
|
map_literal, storage.Create<PrimitiveLiteral>(TypedValue::Null));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, ListSlicingOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
|
|
storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(3),
|
|
storage.Create<PrimitiveLiteral>(4)});
|
|
|
|
auto extract_ints = [](TypedValue list) {
|
|
std::vector<int64_t> int_list;
|
|
for (auto x : list.Value<std::vector<TypedValue>>()) {
|
|
int_list.push_back(x.Value<int64_t>());
|
|
}
|
|
return int_list;
|
|
};
|
|
{
|
|
// Legal slicing with both bounds defined.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(4));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
|
|
}
|
|
{
|
|
// Legal slicing with negative bound.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(-1));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre(3));
|
|
}
|
|
{
|
|
// Lower bound larger than upper bound.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(-4));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre());
|
|
}
|
|
{
|
|
// Bounds ouf or range.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(-100),
|
|
storage.Create<PrimitiveLiteral>(10));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3, 4));
|
|
}
|
|
{
|
|
// Lower bound undefined.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, nullptr, storage.Create<PrimitiveLiteral>(3));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3));
|
|
}
|
|
{
|
|
// Upper bound undefined.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(-2), nullptr);
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
|
|
}
|
|
{
|
|
// Bound of illegal type and null value bound.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
|
storage.Create<PrimitiveLiteral>("mirko"));
|
|
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
{
|
|
// List of illegal type.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
storage.Create<PrimitiveLiteral>("a"),
|
|
storage.Create<PrimitiveLiteral>(-2), nullptr);
|
|
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
{
|
|
// Null value list with undefined upper bound.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
|
storage.Create<PrimitiveLiteral>(-2), nullptr);
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.type(), TypedValue::Type::Null);
|
|
}
|
|
{
|
|
// Null value index.
|
|
auto *op = storage.Create<ListSlicingOperator>(
|
|
list_literal, storage.Create<PrimitiveLiteral>(-2),
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null));
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.type(), TypedValue::Type::Null);
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, IfOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *then_expression = storage.Create<PrimitiveLiteral>(10);
|
|
auto *else_expression = storage.Create<PrimitiveLiteral>(20);
|
|
{
|
|
auto *condition_true =
|
|
storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(2));
|
|
auto *op = storage.Create<IfOperator>(condition_true, then_expression,
|
|
else_expression);
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 10);
|
|
}
|
|
{
|
|
auto *condition_false =
|
|
storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(3));
|
|
auto *op = storage.Create<IfOperator>(condition_false, then_expression,
|
|
else_expression);
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 20);
|
|
}
|
|
{
|
|
auto *condition_exception =
|
|
storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2),
|
|
storage.Create<PrimitiveLiteral>(3));
|
|
auto *op = storage.Create<IfOperator>(condition_exception, then_expression,
|
|
else_expression);
|
|
ASSERT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, NotOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<NotOperator>(storage.Create<PrimitiveLiteral>(false));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<bool>(), true);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, UnaryPlusOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<UnaryPlusOperator>(storage.Create<PrimitiveLiteral>(5));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), 5);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, UnaryMinusOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<UnaryMinusOperator>(storage.Create<PrimitiveLiteral>(5));
|
|
auto value = op->Accept(eval.eval);
|
|
ASSERT_EQ(value.Value<int64_t>(), -5);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, IsNullOperator) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *op =
|
|
storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(1));
|
|
auto val1 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val1.Value<bool>(), false);
|
|
op = storage.Create<IsNullOperator>(
|
|
storage.Create<PrimitiveLiteral>(TypedValue::Null));
|
|
auto val2 = op->Accept(eval.eval);
|
|
ASSERT_EQ(val2.Value<bool>(), true);
|
|
}
|
|
|
|
class ExpressionEvaluatorPropertyLookup : public testing::Test {
|
|
protected:
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
Dbms dbms;
|
|
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
|
|
std::pair<std::string, GraphDbTypes::Property> prop_age =
|
|
PROPERTY_PAIR("age");
|
|
std::pair<std::string, GraphDbTypes::Property> prop_height =
|
|
PROPERTY_PAIR("height");
|
|
Expression *identifier = storage.Create<Identifier>("element");
|
|
Symbol symbol = eval.symbol_table.CreateSymbol("element", true);
|
|
|
|
void SetUp() { eval.symbol_table[*identifier] = symbol; }
|
|
|
|
auto Value(std::pair<std::string, GraphDbTypes::Property> property) {
|
|
auto *op = storage.Create<PropertyLookup>(identifier, property);
|
|
return op->Accept(eval.eval);
|
|
}
|
|
};
|
|
|
|
TEST_F(ExpressionEvaluatorPropertyLookup, Vertex) {
|
|
auto v1 = dba->InsertVertex();
|
|
v1.PropsSet(prop_age.second, 10);
|
|
eval.frame[symbol] = v1;
|
|
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
|
EXPECT_TRUE(Value(prop_height).IsNull());
|
|
}
|
|
|
|
TEST_F(ExpressionEvaluatorPropertyLookup, Edge) {
|
|
auto v1 = dba->InsertVertex();
|
|
auto v2 = dba->InsertVertex();
|
|
auto e12 = dba->InsertEdge(v1, v2, dba->EdgeType("edge_type"));
|
|
e12.PropsSet(prop_age.second, 10);
|
|
eval.frame[symbol] = e12;
|
|
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
|
EXPECT_TRUE(Value(prop_height).IsNull());
|
|
}
|
|
|
|
TEST_F(ExpressionEvaluatorPropertyLookup, Null) {
|
|
eval.frame[symbol] = TypedValue::Null;
|
|
EXPECT_TRUE(Value(prop_age).IsNull());
|
|
}
|
|
|
|
TEST_F(ExpressionEvaluatorPropertyLookup, MapLiteral) {
|
|
eval.frame[symbol] = std::map<std::string, TypedValue>{{prop_age.first, 10}};
|
|
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
|
EXPECT_TRUE(Value(prop_height).IsNull());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, LabelsTest) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
Dbms dbms;
|
|
auto dba = dbms.active();
|
|
auto v1 = dba->InsertVertex();
|
|
v1.add_label(dba->Label("ANIMAL"));
|
|
v1.add_label(dba->Label("DOG"));
|
|
v1.add_label(dba->Label("NICE_DOG"));
|
|
auto *identifier = storage.Create<Identifier>("n");
|
|
auto node_symbol = eval.symbol_table.CreateSymbol("n", true);
|
|
eval.symbol_table[*identifier] = node_symbol;
|
|
eval.frame[node_symbol] = v1;
|
|
{
|
|
auto *op = storage.Create<LabelsTest>(
|
|
identifier, std::vector<GraphDbTypes::Label>{dba->Label("DOG"),
|
|
dba->Label("ANIMAL")});
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<bool>(), true);
|
|
}
|
|
{
|
|
auto *op = storage.Create<LabelsTest>(
|
|
identifier,
|
|
std::vector<GraphDbTypes::Label>{
|
|
dba->Label("DOG"), dba->Label("BAD_DOG"), dba->Label("ANIMAL")});
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_EQ(value.Value<bool>(), false);
|
|
}
|
|
{
|
|
eval.frame[node_symbol] = TypedValue::Null;
|
|
auto *op = storage.Create<LabelsTest>(
|
|
identifier,
|
|
std::vector<GraphDbTypes::Label>{
|
|
dba->Label("DOG"), dba->Label("BAD_DOG"), dba->Label("ANIMAL")});
|
|
auto value = op->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, Aggregation) {
|
|
AstTreeStorage storage;
|
|
auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42),
|
|
nullptr, Aggregation::Op::COUNT);
|
|
SymbolTable symbol_table;
|
|
auto aggr_sym = symbol_table.CreateSymbol("aggr", true);
|
|
symbol_table[*aggr] = aggr_sym;
|
|
Frame frame{symbol_table.max_position()};
|
|
frame[aggr_sym] = TypedValue(1);
|
|
Dbms dbms;
|
|
auto dba = dbms.active();
|
|
Parameters parameters;
|
|
ExpressionEvaluator eval{frame, parameters, symbol_table, *dba};
|
|
auto value = aggr->Accept(eval);
|
|
EXPECT_EQ(value.Value<int64_t>(), 1);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, ListLiteral) {
|
|
AstTreeStorage storage;
|
|
NoContextExpressionEvaluator eval;
|
|
auto *list_literal = storage.Create<ListLiteral>(
|
|
std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1),
|
|
storage.Create<PrimitiveLiteral>("bla"),
|
|
storage.Create<PrimitiveLiteral>(true)});
|
|
TypedValue result = list_literal->Accept(eval.eval);
|
|
ASSERT_EQ(result.type(), TypedValue::Type::List);
|
|
auto &result_elems = result.Value<std::vector<TypedValue>>();
|
|
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<int64_t>(),
|
|
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->InsertVertex();
|
|
v1.add_label(dba->Label("label1"));
|
|
auto v2 = dba->InsertVertex();
|
|
v2.add_label(dba->Label("label2"));
|
|
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("t"));
|
|
ASSERT_TRUE(EvaluateFunction("ENDNODE", {e})
|
|
.Value<VertexAccessor>()
|
|
.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<TypedValue> arguments;
|
|
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
|
ASSERT_EQ(EvaluateFunction("HEAD", arguments).Value<int64_t>(), 3);
|
|
arguments[0].Value<std::vector<TypedValue>>().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->InsertVertex();
|
|
v1.PropsSet(dba->Property("height"), 5);
|
|
v1.PropsSet(dba->Property("age"), 10);
|
|
auto v2 = dba->InsertVertex();
|
|
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("type1"));
|
|
e.PropsSet(dba->Property("height"), 3);
|
|
e.PropsSet(dba->Property("age"), 15);
|
|
|
|
auto prop_values_to_int = [](TypedValue t) {
|
|
std::unordered_map<std::string, int> properties;
|
|
for (auto property : t.Value<std::map<std::string, TypedValue>>()) {
|
|
properties[property.first] = property.second.Value<int64_t>();
|
|
}
|
|
return properties;
|
|
};
|
|
ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {v1})),
|
|
UnorderedElementsAre(testing::Pair("height", 5),
|
|
testing::Pair("age", 10)));
|
|
ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {e})),
|
|
UnorderedElementsAre(testing::Pair("height", 3),
|
|
testing::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<TypedValue> arguments;
|
|
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
|
ASSERT_EQ(EvaluateFunction("LAST", arguments).Value<int64_t>(), 5);
|
|
arguments[0].Value<std::vector<TypedValue>>().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<TypedValue> arguments;
|
|
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
|
ASSERT_EQ(EvaluateFunction("SIZE", arguments).Value<int64_t>(), 3);
|
|
ASSERT_EQ(EvaluateFunction("SIZE", {"john"}).Value<int64_t>(), 4);
|
|
ASSERT_EQ(EvaluateFunction("SIZE", {std::map<std::string, TypedValue>{
|
|
{"a", 5}, {"b", true}, {"c", "123"}}})
|
|
.Value<int64_t>(),
|
|
3);
|
|
ASSERT_THROW(EvaluateFunction("SIZE", {5}), QueryRuntimeException);
|
|
|
|
Dbms dbms;
|
|
auto dba = dbms.active();
|
|
auto v0 = dba->InsertVertex();
|
|
query::Path path(v0);
|
|
EXPECT_EQ(EvaluateFunction("SIZE", {path}).ValueInt(), 0);
|
|
auto v1 = dba->InsertVertex();
|
|
path.Expand(dba->InsertEdge(v0, v1, dba->EdgeType("type")));
|
|
path.Expand(v1);
|
|
EXPECT_EQ(EvaluateFunction("SIZE", {path}).ValueInt(), 1);
|
|
}
|
|
|
|
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->InsertVertex();
|
|
v1.add_label(dba->Label("label1"));
|
|
auto v2 = dba->InsertVertex();
|
|
v2.add_label(dba->Label("label2"));
|
|
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("t"));
|
|
ASSERT_TRUE(EvaluateFunction("STARTNODE", {e})
|
|
.Value<VertexAccessor>()
|
|
.has_label(dba->Label("label1")));
|
|
ASSERT_THROW(EvaluateFunction("STARTNODE", {2}), QueryRuntimeException);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionDegree) {
|
|
ASSERT_THROW(EvaluateFunction("DEGREE", {}), QueryRuntimeException);
|
|
ASSERT_EQ(EvaluateFunction("DEGREE", {TypedValue::Null}).type(),
|
|
TypedValue::Type::Null);
|
|
Dbms dbms;
|
|
auto dba = dbms.active();
|
|
auto v1 = dba->InsertVertex();
|
|
auto v2 = dba->InsertVertex();
|
|
auto v3 = dba->InsertVertex();
|
|
auto e12 = dba->InsertEdge(v1, v2, dba->EdgeType("t"));
|
|
dba->InsertEdge(v3, v2, dba->EdgeType("t"));
|
|
ASSERT_EQ(EvaluateFunction("DEGREE", {v1}).Value<int64_t>(), 1);
|
|
ASSERT_EQ(EvaluateFunction("DEGREE", {v2}).Value<int64_t>(), 2);
|
|
ASSERT_EQ(EvaluateFunction("DEGREE", {v3}).Value<int64_t>(), 1);
|
|
ASSERT_THROW(EvaluateFunction("DEGREE", {2}), QueryRuntimeException);
|
|
ASSERT_THROW(EvaluateFunction("DEGREE", {e12}), 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<bool>(), true);
|
|
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFalsE "}).Value<bool>(), false);
|
|
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFALSEA "}).type(),
|
|
TypedValue::Type::Null);
|
|
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {true}).Value<bool>(), true);
|
|
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {false}).Value<bool>(), 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<double>(), -3.5);
|
|
ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t0.5e-1"}).Value<double>(), 0.05);
|
|
ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t3.4e-3X "}).type(),
|
|
TypedValue::Type::Null);
|
|
ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3.5}).Value<double>(), -3.5);
|
|
ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3}).Value<double>(), -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<int64_t>(), 3);
|
|
ASSERT_EQ(EvaluateFunction("TOINTEGER", {" -3.5 \n\t"}).Value<int64_t>(), -3);
|
|
ASSERT_EQ(EvaluateFunction("TOINTEGER", {"\n\t3X "}).type(),
|
|
TypedValue::Type::Null);
|
|
ASSERT_EQ(EvaluateFunction("TOINTEGER", {-3.5}).Value<int64_t>(), -3);
|
|
ASSERT_EQ(EvaluateFunction("TOINTEGER", {3.5}).Value<int64_t>(), 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->InsertVertex();
|
|
v1.add_label(dba->Label("label1"));
|
|
auto v2 = dba->InsertVertex();
|
|
v2.add_label(dba->Label("label2"));
|
|
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("type1"));
|
|
ASSERT_EQ(EvaluateFunction("TYPE", {e}).Value<std::string>(), "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->InsertVertex();
|
|
v.add_label(dba->Label("label1"));
|
|
v.add_label(dba->Label("label2"));
|
|
std::vector<std::string> labels;
|
|
auto _labels =
|
|
EvaluateFunction("LABELS", {v}).Value<std::vector<TypedValue>>();
|
|
for (auto label : _labels) {
|
|
labels.push_back(label.Value<std::string>());
|
|
}
|
|
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);
|
|
EXPECT_THROW(EvaluateFunction("RANGE", {1, 2, 0}), QueryRuntimeException);
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {1, 3})),
|
|
ElementsAre(1, 2, 3));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {-1, 5, 2})),
|
|
ElementsAre(-1, 1, 3, 5));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {2, 10, 3})),
|
|
ElementsAre(2, 5, 8));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {2, 2, 2})),
|
|
ElementsAre(2));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {3, 0, 5})),
|
|
ElementsAre());
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {5, 1, -2})),
|
|
ElementsAre(5, 3, 1));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {6, 1, -2})),
|
|
ElementsAre(6, 4, 2));
|
|
EXPECT_THAT(ToList<int64_t>(EvaluateFunction("RANGE", {2, 2, -3})),
|
|
ElementsAre(2));
|
|
EXPECT_THAT(ToList<int64_t>(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->InsertVertex();
|
|
v1.PropsSet(dba->Property("height"), 5);
|
|
v1.PropsSet(dba->Property("age"), 10);
|
|
auto v2 = dba->InsertVertex();
|
|
auto e = dba->InsertEdge(v1, v2, dba->EdgeType("type1"));
|
|
e.PropsSet(dba->Property("width"), 3);
|
|
e.PropsSet(dba->Property("age"), 15);
|
|
|
|
auto prop_keys_to_string = [](TypedValue t) {
|
|
std::vector<std::string> keys;
|
|
for (auto property : t.Value<std::vector<TypedValue>>()) {
|
|
keys.push_back(property.Value<std::string>());
|
|
}
|
|
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<TypedValue> arguments;
|
|
arguments.push_back(std::vector<TypedValue>{});
|
|
ASSERT_EQ(EvaluateFunction("TAIL", arguments)
|
|
.Value<std::vector<TypedValue>>()
|
|
.size(),
|
|
0U);
|
|
arguments[0] = std::vector<TypedValue>{3, 4, true, "john"};
|
|
auto list =
|
|
EvaluateFunction("TAIL", arguments).Value<std::vector<TypedValue>>();
|
|
ASSERT_EQ(list.size(), 3U);
|
|
ASSERT_EQ(list[0].Value<int64_t>(), 4);
|
|
ASSERT_EQ(list[1].Value<bool>(), true);
|
|
ASSERT_EQ(list[2].Value<std::string>(), "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<int64_t>(), 2);
|
|
ASSERT_EQ(EvaluateFunction("ABS", {-2.5}).Value<double>(), 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<double>(), log(2));
|
|
ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", {1.5}).Value<double>(), log(1.5));
|
|
// Not portable, but should work on most platforms.
|
|
ASSERT_TRUE(std::isnan(EvaluateFunction("LOG", {-1.5}).Value<double>()));
|
|
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<double>(), -2);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {-2.4}).Value<double>(), -2);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {-2.5}).Value<double>(), -3);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {-2.6}).Value<double>(), -3);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {2.4}).Value<double>(), 2);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {2.5}).Value<double>(), 3);
|
|
ASSERT_EQ(EvaluateFunction("ROUND", {2.6}).Value<double>(), 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<double>(),
|
|
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<int64_t>(), -1);
|
|
ASSERT_EQ(EvaluateFunction("SIGN", {-0.2}).Value<int64_t>(), -1);
|
|
ASSERT_EQ(EvaluateFunction("SIGN", {0.0}).Value<int64_t>(), 0);
|
|
ASSERT_EQ(EvaluateFunction("SIGN", {2.5}).Value<int64_t>(), 1);
|
|
ASSERT_THROW(EvaluateFunction("SIGN", {true}), QueryRuntimeException);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionE) {
|
|
ASSERT_THROW(EvaluateFunction("E", {1}), QueryRuntimeException);
|
|
ASSERT_DOUBLE_EQ(EvaluateFunction("E", {}).Value<double>(), M_E);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionPi) {
|
|
ASSERT_THROW(EvaluateFunction("PI", {1}), QueryRuntimeException);
|
|
ASSERT_DOUBLE_EQ(EvaluateFunction("PI", {}).Value<double>(), M_PI);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionRand) {
|
|
ASSERT_THROW(EvaluateFunction("RAND", {1}), QueryRuntimeException);
|
|
ASSERT_GE(EvaluateFunction("RAND", {}).Value<double>(), 0.0);
|
|
ASSERT_LT(EvaluateFunction("RAND", {}).Value<double>(), 1.0);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionStartsWith) {
|
|
EXPECT_THROW(EvaluateFunction(kStartsWith, {}), QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kStartsWith, {"a", TypedValue::Null}).IsNull());
|
|
EXPECT_THROW(EvaluateFunction(kStartsWith, {TypedValue::Null, 1.3}),
|
|
QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kStartsWith, {"abc", "abc"}).Value<bool>());
|
|
EXPECT_TRUE(EvaluateFunction(kStartsWith, {"abcdef", "abc"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kStartsWith, {"abcdef", "aBc"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kStartsWith, {"abc", "abcd"}).Value<bool>());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionEndsWith) {
|
|
EXPECT_THROW(EvaluateFunction(kEndsWith, {}), QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kEndsWith, {"a", TypedValue::Null}).IsNull());
|
|
EXPECT_THROW(EvaluateFunction(kEndsWith, {TypedValue::Null, 1.3}),
|
|
QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kEndsWith, {"abc", "abc"}).Value<bool>());
|
|
EXPECT_TRUE(EvaluateFunction(kEndsWith, {"abcdef", "def"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kEndsWith, {"abcdef", "dEf"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kEndsWith, {"bcd", "abcd"}).Value<bool>());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionContains) {
|
|
EXPECT_THROW(EvaluateFunction(kContains, {}), QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kContains, {"a", TypedValue::Null}).IsNull());
|
|
EXPECT_THROW(EvaluateFunction(kContains, {TypedValue::Null, 1.3}),
|
|
QueryRuntimeException);
|
|
EXPECT_TRUE(EvaluateFunction(kContains, {"abc", "abc"}).Value<bool>());
|
|
EXPECT_TRUE(EvaluateFunction(kContains, {"abcde", "bcd"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kContains, {"cde", "abcdef"}).Value<bool>());
|
|
EXPECT_FALSE(EvaluateFunction(kContains, {"abcdef", "dEf"}).Value<bool>());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionAll) {
|
|
AstTreeStorage storage;
|
|
auto *ident_x = IDENT("x");
|
|
auto *all =
|
|
ALL("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
|
|
NoContextExpressionEvaluator eval;
|
|
const auto x_sym = eval.symbol_table.CreateSymbol("x", true);
|
|
eval.symbol_table[*all->identifier_] = x_sym;
|
|
eval.symbol_table[*ident_x] = x_sym;
|
|
auto value = all->Accept(eval.eval);
|
|
ASSERT_EQ(value.type(), TypedValue::Type::Bool);
|
|
EXPECT_FALSE(value.Value<bool>());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionAllNullList) {
|
|
AstTreeStorage storage;
|
|
auto *all = ALL("x", LITERAL(TypedValue::Null), WHERE(LITERAL(true)));
|
|
NoContextExpressionEvaluator eval;
|
|
const auto x_sym = eval.symbol_table.CreateSymbol("x", true);
|
|
eval.symbol_table[*all->identifier_] = x_sym;
|
|
auto value = all->Accept(eval.eval);
|
|
EXPECT_TRUE(value.IsNull());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionAllWhereWrongType) {
|
|
AstTreeStorage storage;
|
|
auto *all = ALL("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
|
|
NoContextExpressionEvaluator eval;
|
|
const auto x_sym = eval.symbol_table.CreateSymbol("x", true);
|
|
eval.symbol_table[*all->identifier_] = x_sym;
|
|
EXPECT_THROW(all->Accept(eval.eval), QueryRuntimeException);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionAssert) {
|
|
// Invalid calls.
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {}), QueryRuntimeException);
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, false}),
|
|
QueryRuntimeException);
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {"string", false}),
|
|
QueryRuntimeException);
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "reason", true}),
|
|
QueryRuntimeException);
|
|
|
|
// Valid calls, assertion fails.
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false}), QueryRuntimeException);
|
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "message"}),
|
|
QueryRuntimeException);
|
|
try {
|
|
EvaluateFunction("ASSERT", {false, "bbgba"});
|
|
} catch (QueryRuntimeException &e) {
|
|
ASSERT_TRUE(utils::EndsWith(e.what(), "bbgba"));
|
|
}
|
|
|
|
// Valid calls, assertion passes.
|
|
ASSERT_TRUE(EvaluateFunction("ASSERT", {true}).ValueBool());
|
|
ASSERT_TRUE(EvaluateFunction("ASSERT", {true, "message"}).ValueBool());
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, ParameterLookup) {
|
|
NoContextExpressionEvaluator eval;
|
|
eval.parameters.Add(0, 42);
|
|
AstTreeStorage storage;
|
|
auto *param_lookup = storage.Create<ParameterLookup>(0);
|
|
auto value = param_lookup->Accept(eval.eval);
|
|
ASSERT_EQ(value.type(), TypedValue::Type::Int);
|
|
EXPECT_EQ(value.Value<int64_t>(), 42);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionCounter) {
|
|
Dbms dbms;
|
|
EXPECT_THROW(EvaluateFunction("COUNTER", {}, dbms), QueryRuntimeException);
|
|
EXPECT_THROW(EvaluateFunction("COUNTER", {"a", "b"}, dbms),
|
|
QueryRuntimeException);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 0);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 1);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 0);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 2);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 1);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionCounterSet) {
|
|
Dbms dbms;
|
|
EXPECT_THROW(EvaluateFunction("COUNTERSET", {}, dbms), QueryRuntimeException);
|
|
EXPECT_THROW(EvaluateFunction("COUNTERSET", {"a"}, dbms),
|
|
QueryRuntimeException);
|
|
EXPECT_THROW(EvaluateFunction("COUNTERSET", {"a", "b"}, dbms),
|
|
QueryRuntimeException);
|
|
EXPECT_THROW(EvaluateFunction("COUNTERSET", {"a", 11, 12}, dbms),
|
|
QueryRuntimeException);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 0);
|
|
EvaluateFunction("COUNTERSET", {"c1", 12}, dbms);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 12);
|
|
EvaluateFunction("COUNTERSET", {"c2", 42}, dbms);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 42);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 13);
|
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 43);
|
|
}
|
|
|
|
TEST(ExpressionEvaluator, FunctionIndexInfo) {
|
|
Dbms dbms;
|
|
EXPECT_THROW(EvaluateFunction("INDEXINFO", {1}, dbms), QueryRuntimeException);
|
|
EXPECT_EQ(EvaluateFunction("INDEXINFO", {}, dbms).ValueList().size(), 0);
|
|
auto dba = dbms.active();
|
|
dba->InsertVertex().add_label(dba->Label("l1"));
|
|
{
|
|
auto info = ToList<std::string>(EvaluateFunction("INDEXINFO", {}, dbms));
|
|
EXPECT_EQ(info.size(), 1);
|
|
EXPECT_EQ(info[0], ":l1");
|
|
}
|
|
{
|
|
dba->BuildIndex(dba->Label("l1"), dba->Property("prop"));
|
|
auto info = ToList<std::string>(EvaluateFunction("INDEXINFO", {}, dbms));
|
|
EXPECT_EQ(info.size(), 2);
|
|
EXPECT_THAT(info, testing::UnorderedElementsAre(":l1", ":l1(prop)"));
|
|
}
|
|
}
|
|
} // namespace
|