Implement evaluation of list indexing and slicing

Reviewers: florijan, teon.banek

Reviewed By: florijan

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D342
This commit is contained in:
Mislav Bradac 2017-05-04 15:16:57 +02:00
parent d2f9affb4c
commit 2e9c0e4cb9
2 changed files with 230 additions and 5 deletions

View File

@ -1,8 +1,9 @@
#pragma once
#include <algorithm>
#include <limits>
#include <map>
#include <vector>
#include <algorithm>
#include "database/graph_db_accessor.hpp"
#include "query/common.hpp"
@ -85,14 +86,92 @@ class ExpressionEvaluator : public TreeVisitorBase {
UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +);
UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -);
#undef BINARY_OPERATOR_VISITOR
#undef UNARY_OPERATOR_VISITOR
void PostVisit(ListIndexingOperator &) override {
// TODO: implement this for maps
auto _index = PopBack();
if (_index.type() != TypedValue::Type::Int &&
_index.type() != TypedValue::Type::Null) {
throw TypedValueException("Incompatible type in list lookup");
}
auto _list = PopBack();
if (_list.type() != TypedValue::Type::List &&
_list.type() != TypedValue::Type::Null) {
throw TypedValueException("Incompatible type in list lookup");
}
if (_index.type() == TypedValue::Type::Null ||
_list.type() == TypedValue::Type::Null) {
result_stack_.emplace_back(TypedValue::Null);
return;
}
auto index = _index.Value<int64_t>();
const auto &list = _list.Value<std::vector<TypedValue>>();
if (index < 0) {
index = static_cast<int64_t>(list.size()) + index;
}
if (index >= static_cast<int64_t>(list.size()) || index < 0) {
result_stack_.emplace_back(TypedValue::Null);
return;
}
result_stack_.emplace_back(list[index]);
}
void PostVisit(ListSlicingOperator &op) override {
// If some type is null we can't return null, because throwing exception on
// illegal type has higher priority.
auto is_null = false;
auto get_bound = [&](bool is_defined, int64_t default_value) {
if (is_defined) {
auto bound = PopBack();
if (bound.type() == TypedValue::Type::Null) {
is_null = true;
} else if (bound.type() != TypedValue::Type::Int) {
throw TypedValueException("Incompatible type in list slicing");
}
return bound;
}
return TypedValue(default_value);
};
auto _upper_bound =
get_bound(op.upper_bound_, std::numeric_limits<int64_t>::max());
auto _lower_bound = get_bound(op.lower_bound_, 0);
auto _list = PopBack();
if (_list.type() == TypedValue::Type::Null) {
is_null = true;
} else if (_list.type() != TypedValue::Type::List) {
throw TypedValueException("Incompatible type in list slicing");
}
if (is_null) {
result_stack_.emplace_back(TypedValue::Null);
return;
}
const auto &list = _list.Value<std::vector<TypedValue>>();
auto normalise_bound = [&](int64_t bound) {
if (bound < 0) {
bound = static_cast<int64_t>(list.size()) + bound;
}
return std::max(static_cast<int64_t>(0),
std::min(bound, static_cast<int64_t>(list.size())));
};
auto lower_bound = normalise_bound(_lower_bound.Value<int64_t>());
auto upper_bound = normalise_bound(_upper_bound.Value<int64_t>());
if (upper_bound <= lower_bound) {
result_stack_.emplace_back(std::vector<TypedValue>());
return;
}
result_stack_.emplace_back(std::vector<TypedValue>(
list.begin() + lower_bound, list.begin() + upper_bound));
}
void PostVisit(IsNullOperator &) override {
auto expression = PopBack();
result_stack_.push_back(TypedValue(expression.IsNull()));
}
#undef BINARY_OPERATOR_VISITOR
#undef UNARY_OPERATOR_VISITOR
void PostVisit(PropertyLookup &property_lookup) override {
auto expression_result = PopBack();
switch (expression_result.type()) {
@ -130,7 +209,7 @@ class ExpressionEvaluator : public TreeVisitorBase {
void PostVisit(ListLiteral &literal) override {
std::vector<TypedValue> result;
result.reserve(literal.elements_.size());
for (size_t i = 0 ; i < literal.elements_.size() ; i++)
for (size_t i = 0; i < literal.elements_.size(); i++)
result.emplace_back(PopBack());
std::reverse(result.begin(), result.end());
result_stack_.emplace_back(std::move(result));

View File

@ -16,6 +16,7 @@
using namespace query;
using testing::Pair;
using testing::UnorderedElementsAre;
using testing::ElementsAre;
namespace {
@ -242,6 +243,151 @@ TEST(ExpressionEvaluator, GreaterEqualOperator) {
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
}
TEST(ExpressionEvaluator, ListIndexingOperator) {
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<ListIndexingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(2));
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().Value<int64_t>(), 3);
}
{
// Out of bounds indexing.
auto *op = storage.Create<ListIndexingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(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<ListIndexingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(-100));
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null);
}
{
// Legal indexing with negative index.
auto *op = storage.Create<ListIndexingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(-2));
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().Value<int64_t>(), 3);
}
{
// Indexing with one operator being null.
auto *op = storage.Create<ListIndexingOperator>(
storage.Create<PrimitiveLiteral>(TypedValue::Null),
storage.Create<PrimitiveLiteral>(-2));
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null);
}
{
// Indexing with incompatible type.
auto *op = storage.Create<ListIndexingOperator>(
storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(TypedValue::Null));
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
}
}
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));
op->Accept(eval.eval);
EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(3, 4));
}
{
// Legal slicing with negative bound.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(-1));
op->Accept(eval.eval);
EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(3));
}
{
// Lower bound larger than upper bound.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(-4));
op->Accept(eval.eval);
EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre());
}
{
// Bounds ouf or range.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(-100),
storage.Create<PrimitiveLiteral>(10));
op->Accept(eval.eval);
EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(1, 2, 3, 4));
}
{
// Lower bound undefined.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, nullptr, storage.Create<PrimitiveLiteral>(3));
op->Accept(eval.eval);
EXPECT_THAT(extract_ints(eval.eval.PopBack()), ElementsAre(1, 2, 3));
}
{
// Upper bound undefined.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(-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<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(TypedValue::Null),
storage.Create<PrimitiveLiteral>("mirko"));
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
}
{
// List of illegal type.
auto *op = storage.Create<ListSlicingOperator>(
storage.Create<PrimitiveLiteral>("a"),
storage.Create<PrimitiveLiteral>(-2), nullptr);
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
}
{
// Null value list with undefined upper bound.
auto *op = storage.Create<ListSlicingOperator>(
storage.Create<PrimitiveLiteral>(TypedValue::Null),
storage.Create<PrimitiveLiteral>(-2), nullptr);
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null);
}
{
// Null value index.
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(-2),
storage.Create<PrimitiveLiteral>(TypedValue::Null));
op->Accept(eval.eval);
EXPECT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null);
}
}
TEST(ExpressionEvaluator, NotOperator) {
AstTreeStorage storage;
NoContextExpressionEvaluator eval;