Map indexing added
Reviewers: mislav.bradac, buda Reviewed By: mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D739
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
* `rand` function added.
|
||||
* Maps can now be stored as vertex/edge properties.
|
||||
* `collect` aggregation now supports Map collection.
|
||||
* Map indexing supported.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
|
||||
@@ -417,7 +417,7 @@ class InListOperator : public BinaryOperator {
|
||||
using BinaryOperator::BinaryOperator;
|
||||
};
|
||||
|
||||
class ListIndexingOperator : public BinaryOperator {
|
||||
class ListMapIndexingOperator : public BinaryOperator {
|
||||
friend class AstTreeStorage;
|
||||
|
||||
public:
|
||||
|
||||
@@ -45,7 +45,7 @@ class GreaterOperator;
|
||||
class LessEqualOperator;
|
||||
class GreaterEqualOperator;
|
||||
class InListOperator;
|
||||
class ListIndexingOperator;
|
||||
class ListMapIndexingOperator;
|
||||
class ListSlicingOperator;
|
||||
class IfOperator;
|
||||
class Delete;
|
||||
@@ -64,7 +64,7 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
FilterAndOperator, NotOperator, AdditionOperator, SubtractionOperator,
|
||||
MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator,
|
||||
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
|
||||
GreaterEqualOperator, InListOperator, ListIndexingOperator,
|
||||
GreaterEqualOperator, InListOperator, ListMapIndexingOperator,
|
||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
|
||||
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
|
||||
EdgeTypeTest, Aggregation, Function, All, Create, Match, Return, With,
|
||||
@@ -89,7 +89,7 @@ using TreeVisitor = ::utils::Visitor<
|
||||
FilterAndOperator, NotOperator, AdditionOperator, SubtractionOperator,
|
||||
MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator,
|
||||
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
|
||||
GreaterEqualOperator, InListOperator, ListIndexingOperator,
|
||||
GreaterEqualOperator, InListOperator, ListMapIndexingOperator,
|
||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
|
||||
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
|
||||
EdgeTypeTest, Aggregation, Function, All, Create, Match, Return, With,
|
||||
|
||||
@@ -714,7 +714,7 @@ antlrcpp::Any CypherMainVisitor::visitExpression3b(
|
||||
for (auto *list_op : ctx->listIndexingOrSlicing()) {
|
||||
if (list_op->getTokens(kDotsTokenId).size() == 0U) {
|
||||
// If there is no '..' then we need to create list indexing operator.
|
||||
expression = storage_.Create<ListIndexingOperator>(
|
||||
expression = storage_.Create<ListMapIndexingOperator>(
|
||||
expression, list_op->expression()[0]->accept(this));
|
||||
} else if (!list_op->lower_bound && !list_op->upper_bound) {
|
||||
throw SemanticException(
|
||||
|
||||
@@ -170,33 +170,40 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
|
||||
return false;
|
||||
}
|
||||
|
||||
TypedValue Visit(ListIndexingOperator &list_indexing) override {
|
||||
// TODO: implement this for maps
|
||||
auto _list = list_indexing.expression1_->Accept(*this);
|
||||
if (_list.type() != TypedValue::Type::List &&
|
||||
_list.type() != TypedValue::Type::Null) {
|
||||
TypedValue Visit(ListMapIndexingOperator &list_indexing) override {
|
||||
auto lhs = list_indexing.expression1_->Accept(*this);
|
||||
auto index = list_indexing.expression2_->Accept(*this);
|
||||
if (!lhs.IsList() && !lhs.IsMap() && !lhs.IsNull())
|
||||
|
||||
throw QueryRuntimeException(
|
||||
"Expected a list to index with '[]', but got {}", _list.type());
|
||||
"Expected a list or map to index with '[]', but got {}", lhs.type());
|
||||
if (lhs.IsNull() || index.IsNull()) return TypedValue::Null;
|
||||
if (lhs.IsList()) {
|
||||
if (!index.IsInt())
|
||||
throw QueryRuntimeException(
|
||||
"Expected an int as a list index, but got {}", index.type());
|
||||
auto index_int = index.Value<int64_t>();
|
||||
const auto &list = lhs.Value<std::vector<TypedValue>>();
|
||||
if (index_int < 0) {
|
||||
index_int += static_cast<int64_t>(list.size());
|
||||
}
|
||||
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0)
|
||||
return TypedValue::Null;
|
||||
return list[index_int];
|
||||
}
|
||||
auto _index = list_indexing.expression2_->Accept(*this);
|
||||
if (_index.type() != TypedValue::Type::Int &&
|
||||
_index.type() != TypedValue::Type::Null) {
|
||||
throw QueryRuntimeException("Expected an int as a list index, but got {}",
|
||||
_index.type());
|
||||
|
||||
if (lhs.IsMap()) {
|
||||
if (!index.IsString())
|
||||
throw QueryRuntimeException(
|
||||
"Expected a string as a map index, but got {}", index.type());
|
||||
const auto &map = lhs.Value<std::map<std::string, TypedValue>>();
|
||||
auto found = map.find(index.Value<std::string>());
|
||||
if (found == map.end()) return TypedValue::Null;
|
||||
return found->second;
|
||||
}
|
||||
if (_index.type() == TypedValue::Type::Null ||
|
||||
_list.type() == TypedValue::Type::Null) {
|
||||
return TypedValue::Null;
|
||||
}
|
||||
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) {
|
||||
return TypedValue::Null;
|
||||
}
|
||||
return list[index];
|
||||
|
||||
// lhs is Null
|
||||
return TypedValue::Null;
|
||||
}
|
||||
|
||||
TypedValue Visit(ListSlicingOperator &op) override {
|
||||
|
||||
@@ -344,7 +344,7 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
|
||||
VISIT_BINARY_OPERATOR(LessEqualOperator)
|
||||
VISIT_BINARY_OPERATOR(GreaterEqualOperator)
|
||||
VISIT_BINARY_OPERATOR(InListOperator)
|
||||
VISIT_BINARY_OPERATOR(ListIndexingOperator)
|
||||
VISIT_BINARY_OPERATOR(ListMapIndexingOperator)
|
||||
|
||||
#undef VISIT_BINARY_OPERATOR
|
||||
|
||||
|
||||
@@ -33,3 +33,13 @@ Feature: Map operators
|
||||
Then the result should be:
|
||||
| x.a | x.c.d |
|
||||
| 1 | 42 |
|
||||
|
||||
|
||||
Scenario: Map indexing
|
||||
When executing query:
|
||||
"""
|
||||
WITH {a: 1, b: 'bla', c: {d: 42}} AS x RETURN x["a"] as xa, x["c"]["d"] as xcd, x["z"] as xz
|
||||
"""
|
||||
Then the result should be:
|
||||
| xa | xcd | xz |
|
||||
| 1 | 42 | null |
|
||||
|
||||
@@ -473,11 +473,11 @@ TYPED_TEST(CypherMainVisitorTest, ComparisonOperators) {
|
||||
|
||||
#undef CHECK_COMPARISON
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, ListIndexingOperator) {
|
||||
TYPED_TEST(CypherMainVisitorTest, ListIndexing) {
|
||||
TypeParam ast_generator("RETURN [1,2,3] [ 2 ]");
|
||||
auto *query = ast_generator.query_;
|
||||
auto *return_clause = dynamic_cast<Return *>(query->clauses_[0]);
|
||||
auto *list_index_op = dynamic_cast<ListIndexingOperator *>(
|
||||
auto *list_index_op = dynamic_cast<ListMapIndexingOperator *>(
|
||||
return_clause->body_.named_expressions[0]->expression_);
|
||||
ASSERT_TRUE(list_index_op);
|
||||
auto *list = dynamic_cast<ListLiteral *>(list_index_op->expression1_);
|
||||
@@ -532,7 +532,7 @@ TYPED_TEST(CypherMainVisitorTest, InWithListIndexing) {
|
||||
ASSERT_TRUE(literal);
|
||||
EXPECT_EQ(literal->value_.Value<int64_t>(), 1);
|
||||
auto *list_indexing =
|
||||
dynamic_cast<ListIndexingOperator *>(in_list_operator->expression2_);
|
||||
dynamic_cast<ListMapIndexingOperator *>(in_list_operator->expression2_);
|
||||
ASSERT_TRUE(list_indexing);
|
||||
auto *list = dynamic_cast<ListLiteral *>(list_indexing->expression1_);
|
||||
EXPECT_TRUE(list);
|
||||
|
||||
@@ -313,7 +313,7 @@ TEST(ExpressionEvaluator, InListOperator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, ListIndexingOperator) {
|
||||
TEST(ExpressionEvaluator, ListMapIndexingOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
|
||||
@@ -322,35 +322,35 @@ TEST(ExpressionEvaluator, ListIndexingOperator) {
|
||||
storage.Create<PrimitiveLiteral>(4)});
|
||||
{
|
||||
// Legal indexing.
|
||||
auto *op = storage.Create<ListIndexingOperator>(
|
||||
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<ListIndexingOperator>(
|
||||
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<ListIndexingOperator>(
|
||||
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<ListIndexingOperator>(
|
||||
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<ListIndexingOperator>(
|
||||
auto *op = storage.Create<ListMapIndexingOperator>(
|
||||
storage.Create<PrimitiveLiteral>(TypedValue::Null),
|
||||
storage.Create<PrimitiveLiteral>(-2));
|
||||
auto value = op->Accept(eval.eval);
|
||||
@@ -358,13 +358,52 @@ TEST(ExpressionEvaluator, ListIndexingOperator) {
|
||||
}
|
||||
{
|
||||
// Indexing with incompatible type.
|
||||
auto *op = storage.Create<ListIndexingOperator>(
|
||||
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;
|
||||
|
||||
@@ -985,7 +985,7 @@ TEST(TestLogicalPlanner, EmptyListIndexAggregation) {
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto empty_list = LIST();
|
||||
auto group_by_literal = LITERAL(42);
|
||||
QUERY(RETURN(storage.Create<query::ListIndexingOperator>(empty_list, sum),
|
||||
QUERY(RETURN(storage.Create<query::ListMapIndexingOperator>(empty_list, sum),
|
||||
AS("result"), group_by_literal, AS("group_by")));
|
||||
// We expect to group by '42' and the empty list, because it is a
|
||||
// sub-expression of a binary operator which contains an aggregation. This is
|
||||
|
||||
Reference in New Issue
Block a user