Add support for map projection (#892)

This commit is contained in:
Ante Pušić 2023-05-16 20:05:35 +02:00 committed by GitHub
parent 802f8aceda
commit 0d9bd74a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 676 additions and 18 deletions

View File

@ -114,12 +114,18 @@ constexpr utils::TypeInfo query::ListLiteral::kType{utils::TypeId::AST_LIST_LITE
constexpr utils::TypeInfo query::MapLiteral::kType{utils::TypeId::AST_MAP_LITERAL, "MapLiteral",
&query::BaseLiteral::kType};
constexpr utils::TypeInfo query::MapProjectionLiteral::kType{utils::TypeId::AST_MAP_PROJECTION_LITERAL,
"MapProjectionLiteral", &query::BaseLiteral::kType};
constexpr utils::TypeInfo query::Identifier::kType{utils::TypeId::AST_IDENTIFIER, "Identifier",
&query::Expression::kType};
constexpr utils::TypeInfo query::PropertyLookup::kType{utils::TypeId::AST_PROPERTY_LOOKUP, "PropertyLookup",
&query::Expression::kType};
constexpr utils::TypeInfo query::AllPropertiesLookup::kType{utils::TypeId::AST_ALL_PROPERTIES_LOOKUP,
"AllPropertiesLookup", &query::Expression::kType};
constexpr utils::TypeInfo query::LabelsTest::kType{utils::TypeId::AST_LABELS_TEST, "LabelsTest",
&query::Expression::kType};

View File

@ -1063,8 +1063,9 @@ class MapLiteral : public memgraph::query::BaseLiteral {
DEFVISITABLE(ExpressionVisitor<void>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
for (auto pair : elements_)
for (auto pair : elements_) {
if (!pair.second->Accept(visitor)) break;
}
}
return visitor.PostVisit(*this);
}
@ -1087,6 +1088,60 @@ class MapLiteral : public memgraph::query::BaseLiteral {
friend class AstStorage;
};
struct MapProjectionData {
Expression *map_variable;
std::unordered_map<PropertyIx, Expression *> elements;
};
class MapProjectionLiteral : public memgraph::query::BaseLiteral {
public:
static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
MapProjectionLiteral() = default;
DEFVISITABLE(ExpressionVisitor<TypedValue>);
DEFVISITABLE(ExpressionVisitor<TypedValue *>);
DEFVISITABLE(ExpressionVisitor<void>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
for (auto pair : elements_) {
if (!pair.second) continue;
if (!pair.second->Accept(visitor)) break;
}
}
return visitor.PostVisit(*this);
}
Expression *map_variable_;
std::unordered_map<PropertyIx, Expression *> elements_;
MapProjectionLiteral *Clone(AstStorage *storage) const override {
MapProjectionLiteral *object = storage->Create<MapProjectionLiteral>();
object->map_variable_ = map_variable_;
for (const auto &entry : elements_) {
auto key = storage->GetPropertyIx(entry.first.name);
if (!entry.second) {
object->elements_[key] = nullptr;
continue;
}
object->elements_[key] = entry.second->Clone(storage);
}
return object;
}
protected:
explicit MapProjectionLiteral(Expression *map_variable, std::unordered_map<PropertyIx, Expression *> &&elements)
: map_variable_(map_variable), elements_(std::move(elements)) {}
private:
friend class AstStorage;
};
class Identifier : public memgraph::query::Expression {
public:
static const utils::TypeInfo kType;
@ -1158,6 +1213,38 @@ class PropertyLookup : public memgraph::query::Expression {
friend class AstStorage;
};
class AllPropertiesLookup : public memgraph::query::Expression {
public:
static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
AllPropertiesLookup() = default;
DEFVISITABLE(ExpressionVisitor<TypedValue>);
DEFVISITABLE(ExpressionVisitor<TypedValue *>);
DEFVISITABLE(ExpressionVisitor<void>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
expression_->Accept(visitor);
}
return visitor.PostVisit(*this);
}
memgraph::query::Expression *expression_{nullptr};
AllPropertiesLookup *Clone(AstStorage *storage) const override {
AllPropertiesLookup *object = storage->Create<AllPropertiesLookup>();
object->expression_ = expression_ ? expression_->Clone(storage) : nullptr;
return object;
}
protected:
explicit AllPropertiesLookup(Expression *expression) : expression_(expression) {}
private:
friend class AstStorage;
};
class LabelsTest : public memgraph::query::Expression {
public:
static const utils::TypeInfo kType;

View File

@ -22,6 +22,7 @@ class CypherUnion;
class NamedExpression;
class Identifier;
class PropertyLookup;
class AllPropertiesLookup;
class LabelsTest;
class Aggregation;
class Function;
@ -44,6 +45,7 @@ class EdgeAtom;
class PrimitiveLiteral;
class ListLiteral;
class MapLiteral;
class MapProjectionLiteral;
class OrOperator;
class XorOperator;
class AndOperator;
@ -106,9 +108,10 @@ using TreeCompositeVisitor = utils::CompositeVisitor<
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure,
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach, Exists, CallSubquery, CypherQuery>;
MapProjectionLiteral, PropertyLookup, AllPropertiesLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce,
Extract, All, Single, Any, None, CallProcedure, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete,
Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv,
Foreach, Exists, CallSubquery, CypherQuery>;
using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
@ -122,13 +125,14 @@ class HierarchicalTreeVisitor : public TreeCompositeVisitor, public TreeLeafVisi
template <class TResult>
class ExpressionVisitor
: public utils::Visitor<
TResult, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any,
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists> {};
: public utils::Visitor<TResult, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator,
AdditionOperator, SubtractionOperator, MultiplicationOperator, DivisionOperator,
ModOperator, NotEqualOperator, EqualOperator, LessOperator, GreaterOperator,
LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
ListLiteral, MapLiteral, MapProjectionLiteral, PropertyLookup, AllPropertiesLookup,
LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None,
ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists> {};
template <class TResult>
class QueryVisitor

View File

@ -1701,6 +1701,38 @@ antlrcpp::Any CypherMainVisitor::visitMapLiteral(MemgraphCypher::MapLiteralConte
return map;
}
antlrcpp::Any CypherMainVisitor::visitMapProjectionLiteral(MemgraphCypher::MapProjectionLiteralContext *ctx) {
MapProjectionData map_projection_data;
map_projection_data.map_variable =
storage_->Create<Identifier>(std::any_cast<std::string>(ctx->variable()->accept(this)));
for (auto *map_el : ctx->mapElement()) {
if (map_el->propertyLookup()) {
auto key = std::any_cast<PropertyIx>(map_el->propertyLookup()->propertyKeyName()->accept(this));
auto property = std::any_cast<PropertyIx>(map_el->propertyLookup()->accept(this));
auto *property_lookup = storage_->Create<PropertyLookup>(map_projection_data.map_variable, property);
map_projection_data.elements.insert_or_assign(key, property_lookup);
}
if (map_el->allPropertiesLookup()) {
auto key = AddProperty("*");
auto *all_properties_lookup = storage_->Create<AllPropertiesLookup>(map_projection_data.map_variable);
map_projection_data.elements.insert_or_assign(key, all_properties_lookup);
}
if (map_el->variable()) {
auto key = AddProperty(std::any_cast<std::string>(map_el->variable()->accept(this)));
auto *variable = storage_->Create<Identifier>(std::any_cast<std::string>(map_el->variable()->accept(this)));
map_projection_data.elements.insert_or_assign(key, variable);
}
if (map_el->propertyKeyValuePair()) {
auto key = std::any_cast<PropertyIx>(map_el->propertyKeyValuePair()->propertyKeyName()->accept(this));
auto *value = std::any_cast<Expression *>(map_el->propertyKeyValuePair()->expression()->accept(this));
map_projection_data.elements.insert_or_assign(key, value);
}
}
return map_projection_data;
}
antlrcpp::Any CypherMainVisitor::visitListLiteral(MemgraphCypher::ListLiteralContext *ctx) {
std::vector<Expression *> expressions;
for (auto *expr_ctx : ctx->expression()) {
@ -2281,6 +2313,10 @@ antlrcpp::Any CypherMainVisitor::visitLiteral(MemgraphCypher::LiteralContext *ct
} else if (ctx->listLiteral()) {
return static_cast<Expression *>(
storage_->Create<ListLiteral>(std::any_cast<std::vector<Expression *>>(ctx->listLiteral()->accept(this))));
} else if (ctx->mapProjectionLiteral()) {
auto map_projection_data = std::any_cast<MapProjectionData>(ctx->mapProjectionLiteral()->accept(this));
return static_cast<Expression *>(storage_->Create<MapProjectionLiteral>(map_projection_data.map_variable,
std::move(map_projection_data.elements)));
} else {
return static_cast<Expression *>(storage_->Create<MapLiteral>(
std::any_cast<std::unordered_map<PropertyIx, Expression *>>(ctx->mapLiteral()->accept(this))));

View File

@ -608,6 +608,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitMapLiteral(MemgraphCypher::MapLiteralContext *ctx) override;
/**
* @return MapProjectionData
*/
antlrcpp::Any visitMapProjectionLiteral(MemgraphCypher::MapProjectionLiteralContext *ctx) override;
/**
* @return vector<Expression*>
*/

View File

@ -54,6 +54,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
void Visit(IfOperator &op) override;
void Visit(ListLiteral &op) override;
void Visit(MapLiteral &op) override;
void Visit(MapProjectionLiteral &op) override;
void Visit(LabelsTest &op) override;
void Visit(Aggregation &op) override;
void Visit(Function &op) override;
@ -68,6 +69,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
void Visit(Identifier &op) override;
void Visit(PrimitiveLiteral &op) override;
void Visit(PropertyLookup &op) override;
void Visit(AllPropertiesLookup &op) override;
void Visit(ParameterLookup &op) override;
void Visit(NamedExpression &op) override;
void Visit(RegexMatch &op) override;
@ -89,6 +91,8 @@ void PrintObject(std::ostream *out, Aggregation::Op op);
void PrintObject(std::ostream *out, Expression *expr);
void PrintObject(std::ostream *out, AllPropertiesLookup *apl);
void PrintObject(std::ostream *out, Identifier *expr);
void PrintObject(std::ostream *out, const storage::PropertyValue &value);
@ -122,6 +126,15 @@ void PrintObject(std::ostream *out, Expression *expr) {
}
}
void PrintObject(std::ostream *out, AllPropertiesLookup *apl) {
if (apl) {
ExpressionPrettyPrinter printer{out};
*out << ".*";
} else {
*out << "<null>";
}
}
void PrintObject(std::ostream *out, Identifier *expr) { PrintObject(out, static_cast<Expression *>(expr)); }
void PrintObject(std::ostream *out, const storage::PropertyValue &value) {
@ -249,6 +262,17 @@ void ExpressionPrettyPrinter::Visit(MapLiteral &op) {
PrintObject(out_, map);
}
void ExpressionPrettyPrinter::Visit(MapProjectionLiteral &op) {
std::map<std::string, Expression *> map_projection_elements;
for (const auto &kv : op.elements_) {
map_projection_elements[kv.first.name] = kv.second;
}
PrintObject(out_, op.map_variable_);
PrintObject(out_, map_projection_elements);
}
void ExpressionPrettyPrinter::Visit(AllPropertiesLookup &op) { PrintObject(out_, &op); }
void ExpressionPrettyPrinter::Visit(LabelsTest &op) { PrintOperator(out_, "LabelsTest", op.expression_); }
void ExpressionPrettyPrinter::Visit(Aggregation &op) { PrintOperator(out_, "Aggregation", op.op_); }

View File

@ -250,6 +250,7 @@ literal : numberLiteral
| booleanLiteral
| CYPHERNULL
| mapLiteral
| mapProjectionLiteral
| listLiteral
;
@ -292,6 +293,8 @@ patternComprehension : '[' ( variable '=' )? relationshipsPattern ( WHERE expres
propertyLookup : '.' ( propertyKeyName ) ;
allPropertiesLookup : '.' '*' ;
caseExpression : ( ( CASE ( caseAlternatives )+ ) | ( CASE test=expression ( caseAlternatives )+ ) ) ( ELSE else_expression=expression )? END ;
caseAlternatives : WHEN when_expression=expression THEN then_expression=expression ;
@ -304,12 +307,22 @@ numberLiteral : doubleLiteral
mapLiteral : '{' ( propertyKeyName ':' expression ( ',' propertyKeyName ':' expression )* )? '}' ;
mapProjectionLiteral : variable '{' ( mapElement ( ',' mapElement )* )? '}' ;
mapElement : propertyLookup
| allPropertiesLookup
| variable
| propertyKeyValuePair
;
parameter : '$' ( symbolicName | DecimalLiteral ) ;
propertyExpression : atom ( propertyLookup )+ ;
propertyKeyName : symbolicName ;
propertyKeyValuePair : propertyKeyName ':' expression ;
integerLiteral : DecimalLiteral
| OctalLiteral
| HexadecimalLiteral

View File

@ -17,6 +17,7 @@
#include <map>
#include <optional>
#include <regex>
#include <string>
#include <vector>
#include "query/common.hpp"
@ -73,11 +74,13 @@ class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> {
UNSUCCESSFUL_VISIT(ListSlicingOperator);
UNSUCCESSFUL_VISIT(IsNullOperator);
UNSUCCESSFUL_VISIT(PropertyLookup);
UNSUCCESSFUL_VISIT(AllPropertiesLookup);
UNSUCCESSFUL_VISIT(LabelsTest);
UNSUCCESSFUL_VISIT(PrimitiveLiteral);
UNSUCCESSFUL_VISIT(ListLiteral);
UNSUCCESSFUL_VISIT(MapLiteral);
UNSUCCESSFUL_VISIT(MapProjectionLiteral);
UNSUCCESSFUL_VISIT(Aggregation);
UNSUCCESSFUL_VISIT(Coalesce);
UNSUCCESSFUL_VISIT(Function);
@ -466,7 +469,101 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
throw QueryRuntimeException("Invalid property name {} for Graph", prop_name);
}
default:
throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
throw QueryRuntimeException(
"Only nodes, edges, maps, temporal types and graphs have properties to be looked up.");
}
}
TypedValue Visit(AllPropertiesLookup &all_properties_lookup) override {
TypedValue::TMap result(ctx_->memory);
auto expression_result = all_properties_lookup.expression_->Accept(*this);
switch (expression_result.type()) {
case TypedValue::Type::Null:
return TypedValue(ctx_->memory);
case TypedValue::Type::Vertex: {
for (const auto properties = *expression_result.ValueVertex().Properties(view_);
const auto &[property_id, value] : properties) {
result.emplace(dba_->PropertyToName(property_id), value);
}
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::Edge: {
for (const auto properties = *expression_result.ValueEdge().Properties(view_);
const auto &[property_id, value] : properties) {
result.emplace(dba_->PropertyToName(property_id), value);
}
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::Map: {
for (auto &[name, value] : expression_result.ValueMap()) {
result.emplace(name, value);
}
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::Duration: {
const auto &dur = expression_result.ValueDuration();
result.emplace("day", TypedValue(dur.Days(), ctx_->memory));
result.emplace("hour", TypedValue(dur.SubDaysAsHours(), ctx_->memory));
result.emplace("minute", TypedValue(dur.SubDaysAsMinutes(), ctx_->memory));
result.emplace("second", TypedValue(dur.SubDaysAsSeconds(), ctx_->memory));
result.emplace("millisecond", TypedValue(dur.SubDaysAsMilliseconds(), ctx_->memory));
result.emplace("microseconds", TypedValue(dur.SubDaysAsMicroseconds(), ctx_->memory));
result.emplace("nanoseconds", TypedValue(dur.SubDaysAsNanoseconds(), ctx_->memory));
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::Date: {
const auto &date = expression_result.ValueDate();
result.emplace("year", TypedValue(date.year, ctx_->memory));
result.emplace("month", TypedValue(date.month, ctx_->memory));
result.emplace("day", TypedValue(date.day, ctx_->memory));
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::LocalTime: {
const auto &lt = expression_result.ValueLocalTime();
result.emplace("hour", TypedValue(lt.hour, ctx_->memory));
result.emplace("minute", TypedValue(lt.minute, ctx_->memory));
result.emplace("second", TypedValue(lt.second, ctx_->memory));
result.emplace("millisecond", TypedValue(lt.millisecond, ctx_->memory));
result.emplace("microsecond", TypedValue(lt.microsecond, ctx_->memory));
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::LocalDateTime: {
const auto &ldt = expression_result.ValueLocalDateTime();
const auto &date = ldt.date;
const auto &lt = ldt.local_time;
result.emplace("year", TypedValue(date.year, ctx_->memory));
result.emplace("month", TypedValue(date.month, ctx_->memory));
result.emplace("day", TypedValue(date.day, ctx_->memory));
result.emplace("hour", TypedValue(lt.hour, ctx_->memory));
result.emplace("minute", TypedValue(lt.minute, ctx_->memory));
result.emplace("second", TypedValue(lt.second, ctx_->memory));
result.emplace("millisecond", TypedValue(lt.millisecond, ctx_->memory));
result.emplace("microsecond", TypedValue(lt.microsecond, ctx_->memory));
return TypedValue(result, ctx_->memory);
}
case TypedValue::Type::Graph: {
const auto &graph = expression_result.ValueGraph();
utils::pmr::vector<TypedValue> vertices(ctx_->memory);
vertices.reserve(graph.vertices().size());
for (const auto &v : graph.vertices()) {
vertices.emplace_back(TypedValue(v, ctx_->memory));
}
result.emplace("nodes", TypedValue(std::move(vertices), ctx_->memory));
utils::pmr::vector<TypedValue> edges(ctx_->memory);
edges.reserve(graph.edges().size());
for (const auto &e : graph.edges()) {
edges.emplace_back(TypedValue(e, ctx_->memory));
}
result.emplace("edges", TypedValue(std::move(edges), ctx_->memory));
return TypedValue(result, ctx_->memory);
}
default:
throw QueryRuntimeException(
"Only nodes, edges, maps, temporal types and graphs have properties to be looked up.");
}
}
@ -531,6 +628,30 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
return TypedValue(result, ctx_->memory);
}
TypedValue Visit(MapProjectionLiteral &literal) override {
constexpr std::string_view kAllPropertiesSelector{"*"};
TypedValue::TMap result(ctx_->memory);
TypedValue::TMap all_properties_lookup(ctx_->memory);
for (const auto &[property_key, property_value] : literal.elements_) {
if (property_key.name == kAllPropertiesSelector.data()) {
auto maybe_all_properties_lookup = property_value->Accept(*this);
if (maybe_all_properties_lookup.type() != TypedValue::Type::Map) {
throw QueryRuntimeException("Expected a map from AllPropertiesLookup, got {}.",
maybe_all_properties_lookup.type());
}
all_properties_lookup = std::move(maybe_all_properties_lookup.ValueMap());
continue;
}
result.emplace(property_key.name, property_value->Accept(*this));
}
if (!all_properties_lookup.empty()) result.merge(all_properties_lookup);
return TypedValue(result, ctx_->memory);
}
TypedValue Visit(Aggregation &aggregation) override {
return TypedValue(frame_->at(symbol_table_->at(aggregation)), ctx_->memory);
}
@ -852,7 +973,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
DbAccessor *dba_;
// which switching approach should be used when evaluating
storage::View view_;
};
}; // namespace memgraph::query
/// A helper function for evaluating an expression that's an int.
///

View File

@ -180,6 +180,7 @@ class PatternFilterVisitor : public ExpressionVisitor<void> {
void Visit(IfOperator &op) override{};
void Visit(ListLiteral &op) override{};
void Visit(MapLiteral &op) override{};
void Visit(MapProjectionLiteral &op) override{};
void Visit(LabelsTest &op) override{};
void Visit(Aggregation &op) override{};
void Visit(Function &op) override{};
@ -194,6 +195,7 @@ class PatternFilterVisitor : public ExpressionVisitor<void> {
void Visit(Identifier &op) override{};
void Visit(PrimitiveLiteral &op) override{};
void Visit(PropertyLookup &op) override{};
void Visit(AllPropertiesLookup &op) override{};
void Visit(ParameterLookup &op) override{};
void Visit(NamedExpression &op) override{};
void Visit(RegexMatch &op) override{};

View File

@ -124,11 +124,18 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
bool PostVisit(MapLiteral &map_literal) override {
MG_ASSERT(map_literal.elements_.size() <= has_aggregation_.size(),
"Expected has_aggregation_ flags as much as there are map elements.");
"Expected as many has_aggregation_ flags as there are map elements.");
PostVisitCollectionLiteral(map_literal, [](auto it) { return it->second; });
return true;
}
bool PostVisit(MapProjectionLiteral &map_projection_literal) override {
MG_ASSERT(map_projection_literal.elements_.size() <= has_aggregation_.size(),
"Expected as many has_aggregation_ flags as there are map elements.");
PostVisitCollectionLiteral(map_projection_literal, [](auto it) { return it->second; });
return true;
}
bool PostVisit(All &all) override {
// Remove the symbol which is bound by all, because we are only interested
// in free (unbound) symbols.

View File

@ -118,8 +118,10 @@ enum class TypeId : uint64_t {
AST_PRIMITIVE_LITERAL,
AST_LIST_LITERAL,
AST_MAP_LITERAL,
AST_MAP_PROJECTION_LITERAL,
AST_IDENTIFIER,
AST_PROPERTY_LOOKUP,
AST_ALL_PROPERTIES_LOOKUP,
AST_LABELS_TEST,
AST_FUNCTION,
AST_REDUCE,

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -60,6 +60,8 @@ BENCHMARK_TEMPLATE(MapLiteral, NewDeleteResource)->Range(512, 1U << 15U)->Unit(b
BENCHMARK_TEMPLATE(MapLiteral, MonotonicBufferResource)->Range(512, 1U << 15U)->Unit(benchmark::kMicrosecond);
// TODO ante benchmark template for MapProjectionLiteral
template <class TMemory>
// NOLINTNEXTLINE(google-runtime-references)
static void AdditionOperator(benchmark::State &state) {

View File

@ -0,0 +1,76 @@
Feature: Map projection
Scenario: Returning an empty map projection
When executing query:
"""
WITH {} AS map
RETURN map {} as result
"""
Then the result should be:
| result |
| {} |
Scenario: Returning a map projection with each type of map projection element
Given an empty graph
And having executed
"""
CREATE (n:Actor {name: "Morgan", lastName: "Freeman"})
"""
When executing query:
"""
WITH 85 as age
MATCH (actor:Actor)
RETURN actor {.*, .name, age, oscars: 1} AS result
"""
Then the result should be:
| result |
| {age: 85, lastName: 'Freeman', name: 'Morgan', oscars: 1} |
Scenario: Projecting a nonexistent property
When executing query:
"""
WITH {name: "Morgan", lastName: "Freeman"} as actor
RETURN actor.age;
"""
Then the result should be:
| actor.age |
| null |
Scenario: Storing a map projection as a property
Given an empty graph
And having executed
"""
WITH {name: "Morgan", lastName: "Freeman"} as person
WITH person {.*, wonOscars: true} as actor
CREATE (n:Movie {lead: actor});
"""
When executing query:
"""
MATCH (movie:Movie)
RETURN movie.lead
"""
Then the result should be:
| movie.lead |
| {lastName: 'Freeman', name: 'Morgan', wonOscars: true} |
Scenario: Looking up the properties of a map projection
When executing query:
"""
WITH {name: "Morgan", lastName: "Freeman"} as actor, {oscars: 1} as awards
WITH actor {.*, awards: awards} AS actor
RETURN actor.name, actor.awards.oscars;
"""
Then the result should be:
| actor.name | actor.awards.oscars |
| 'Morgan' | 1 |
Scenario: Indexing a map projection
When executing query:
"""
WITH {name: "Morgan", lastName: "Freeman"} as actor, {oscars: 1} as awards
WITH actor {.*, awards: awards} AS actor
RETURN actor["name"], actor["awards"]["oscars"]
"""
Then the result should be:
| actor["name"] | actor["awards"]["oscars"] |
| 'Morgan' | 1 |

View File

@ -1029,6 +1029,63 @@ TEST_P(CypherMainVisitorTest, MapLiteral) {
EXPECT_EQ(1, elem_2_1->elements_.size());
}
TEST_P(CypherMainVisitorTest, MapProjectionLiteral) {
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<CypherQuery *>(ast_generator.ParseQuery(
"WITH {name: \"Morgan\"} as actor, 85 as age RETURN actor {.name, .*, age, lastName: \"Freeman\"}"));
ASSERT_TRUE(query);
ASSERT_TRUE(query->single_query_);
auto *single_query = query->single_query_;
auto *return_clause = dynamic_cast<Return *>(single_query->clauses_[1]);
auto *map_projection_literal =
dynamic_cast<MapProjectionLiteral *>(return_clause->body_.named_expressions[0]->expression_);
ASSERT_TRUE(map_projection_literal);
ASSERT_EQ(4, map_projection_literal->elements_.size());
ASSERT_EQ(std::string(map_projection_literal->elements_[ast_generator.Prop("name")]->GetTypeInfo().name),
std::string("PropertyLookup"));
ASSERT_EQ(std::string(map_projection_literal->elements_[ast_generator.Prop("*")]->GetTypeInfo().name),
std::string("AllPropertiesLookup"));
ASSERT_EQ(std::string(map_projection_literal->elements_[ast_generator.Prop("age")]->GetTypeInfo().name),
std::string("Identifier"));
ASSERT_EQ(std::string(map_projection_literal->elements_[ast_generator.Prop("lastName")]->GetTypeInfo().name),
std::string(typeid(ast_generator).name()).ends_with("CachedAstGenerator")
? std::string("ParameterLookup")
: std::string("PrimitiveLiteral"));
}
TEST_P(CypherMainVisitorTest, MapProjectionRepeatedKeySameTypeValue) {
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("WITH {} as x RETURN x {a: 0, a: 1}"));
ASSERT_TRUE(query);
ASSERT_TRUE(query->single_query_);
auto *single_query = query->single_query_;
auto *return_clause = dynamic_cast<Return *>(single_query->clauses_[1]);
auto *map_projection_literal =
dynamic_cast<MapProjectionLiteral *>(return_clause->body_.named_expressions[0]->expression_);
ASSERT_TRUE(map_projection_literal);
// When multiple map properties have the same name, only one gets in
ASSERT_EQ(1, map_projection_literal->elements_.size());
}
TEST_P(CypherMainVisitorTest, MapProjectionRepeatedKeyDifferentTypeValue) {
auto &ast_generator = *GetParam();
auto *query =
dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("WITH {a: 0} as x, 1 as a RETURN x {a: 2, .a, a}"));
ASSERT_TRUE(query);
ASSERT_TRUE(query->single_query_);
auto *single_query = query->single_query_;
auto *return_clause = dynamic_cast<Return *>(single_query->clauses_[1]);
auto *map_projection_literal =
dynamic_cast<MapProjectionLiteral *>(return_clause->body_.named_expressions[0]->expression_);
ASSERT_TRUE(map_projection_literal);
// When multiple map properties have the same name, only one gets in
ASSERT_EQ(1, map_projection_literal->elements_.size());
// The last-given map property is the one that gets in
ASSERT_EQ(std::string(map_projection_literal->elements_[ast_generator.Prop("a")]->GetTypeInfo().name),
std::string("Identifier"));
}
TEST_P(CypherMainVisitorTest, NodePattern) {
auto &ast_generator = *GetParam();
auto *query =

View File

@ -153,6 +153,14 @@ auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr,
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first));
}
/// Create an AllPropertiesLookup from the given name.
auto GetAllPropertiesLookup(AstStorage &storage, const std::string &name) {
return storage.Create<AllPropertiesLookup>(storage.Create<Identifier>(name));
}
/// Create an AllPropertiesLookup from the given expression.
auto GetAllPropertiesLookup(AstStorage &storage, Expression *expr) { return storage.Create<AllPropertiesLookup>(expr); }
/// Create an EdgeAtom with given name, direction and edge_type.
///
/// Name is used to create the Identifier which is assigned to the edge.
@ -519,8 +527,13 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
#define MAP(...) \
storage.Create<memgraph::query::MapLiteral>( \
std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{__VA_ARGS__})
#define MAP_PROJECTION(map_variable, elements) \
storage.Create<memgraph::query::MapProjectionLiteral>( \
(memgraph::query::Expression *){map_variable}, \
std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{elements})
#define PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToProperty(property_name))
#define PROPERTY_LOOKUP(...) memgraph::query::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__)
#define ALL_PROPERTIES_LOOKUP(expr) memgraph::query::test_common::GetAllPropertiesLookup(storage, expr)
#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::ParameterLookup>((token_position))
#define NEXPR(name, expr) storage.Create<memgraph::query::NamedExpression>((name), (expr))
// AS is alternative to NEXPR which does not initialize NamedExpression with

View File

@ -373,6 +373,81 @@ TEST_F(ExpressionEvaluatorTest, MapIndexing) {
}
}
TEST_F(ExpressionEvaluatorTest, MapProjectionIndexing) {
auto *map_variable = storage.Create<MapLiteral>(
std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}});
auto *map_projection_literal = storage.Create<MapProjectionLiteral>(
map_variable,
std::unordered_map<PropertyIx, Expression *>{
{storage.GetPropertyIx("a"), storage.Create<PrimitiveLiteral>(1)},
{storage.GetPropertyIx("y"), storage.Create<PropertyLookup>(map_variable, storage.GetPropertyIx("y"))}});
{
// Legal indexing.
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("a"));
auto value = Eval(op);
EXPECT_EQ(value.ValueInt(), 1);
}
{
// Legal indexing; property created by PropertyLookup of a non-existent map variable key
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("y"));
auto value = Eval(op);
EXPECT_TRUE(value.IsNull());
}
{
// Legal indexing, non-existing property.
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("z"));
auto value = Eval(op);
EXPECT_TRUE(value.IsNull());
}
{
// Wrong key type.
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>(42));
EXPECT_THROW(Eval(op), QueryRuntimeException);
}
{
// Indexing with Null.
auto *op = storage.Create<SubscriptOperator>(map_projection_literal,
storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()));
auto value = Eval(op);
EXPECT_TRUE(value.IsNull());
}
}
TEST_F(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupBefore) {
// AllPropertiesLookup (.*) may contain properties whose names also occur in MapProjectionLiteral
// The ones in MapProjectionLiteral are explicitly given and thus take precedence over those in AllPropertiesLookup
// Test case: AllPropertiesLookup comes before the identically-named properties
auto *map_variable = storage.Create<MapLiteral>(
std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}});
auto *map_projection_literal = storage.Create<MapProjectionLiteral>(
map_variable, std::unordered_map<PropertyIx, Expression *>{
{storage.GetPropertyIx("*"), storage.Create<AllPropertiesLookup>(map_variable)},
{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(1)}});
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("x"));
auto value = Eval(op);
EXPECT_EQ(value.ValueInt(), 1);
}
TEST_F(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupAfter) {
// AllPropertiesLookup (.*) may contain properties whose names also occur in MapProjectionLiteral
// The ones in MapProjectionLiteral are explicitly given and thus take precedence over those in AllPropertiesLookup
// Test case: AllPropertiesLookup comes after the identically-named properties
auto *map_variable = storage.Create<MapLiteral>(
std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}});
auto *map_projection_literal = storage.Create<MapProjectionLiteral>(
map_variable, std::unordered_map<PropertyIx, Expression *>{
{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(1)},
{storage.GetPropertyIx("*"), storage.Create<AllPropertiesLookup>(map_variable)}});
auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("x"));
auto value = Eval(op);
EXPECT_EQ(value.ValueInt(), 1);
}
TEST_F(ExpressionEvaluatorTest, VertexAndEdgeIndexing) {
auto edge_type = dba.NameToEdgeType("edge_type");
auto prop = dba.NameToProperty("prop");
@ -1197,12 +1272,89 @@ TEST_F(ExpressionEvaluatorPropertyLookup, Null) {
EXPECT_TRUE(Value(prop_age).IsNull());
}
TEST_F(ExpressionEvaluatorPropertyLookup, MapLiteral) {
TEST_F(ExpressionEvaluatorPropertyLookup, Map) {
frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}});
EXPECT_EQ(Value(prop_age).ValueInt(), 10);
EXPECT_TRUE(Value(prop_height).IsNull());
}
class ExpressionEvaluatorAllPropertiesLookup : public ExpressionEvaluatorTest {
protected:
std::pair<std::string, memgraph::storage::PropertyId> prop_age = std::make_pair("age", dba.NameToProperty("age"));
std::pair<std::string, memgraph::storage::PropertyId> prop_height =
std::make_pair("height", dba.NameToProperty("height"));
Identifier *identifier = storage.Create<Identifier>("element");
Symbol symbol = symbol_table.CreateSymbol("element", true);
void SetUp() { identifier->MapTo(symbol); }
auto Value() {
auto *op = storage.Create<AllPropertiesLookup>(identifier);
return Eval(op);
}
};
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Vertex) {
auto v1 = dba.InsertVertex();
ASSERT_TRUE(v1.SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue());
dba.AdvanceCommand();
frame[symbol] = TypedValue(v1);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Edge) {
auto v1 = dba.InsertVertex();
auto v2 = dba.InsertVertex();
auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type"));
ASSERT_TRUE(e12.HasValue());
ASSERT_TRUE(e12->SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue());
dba.AdvanceCommand();
frame[symbol] = TypedValue(*e12);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Duration) {
const memgraph::utils::Duration dur({10, 1, 30, 2, 22, 45});
frame[symbol] = TypedValue(dur);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Date) {
const memgraph::utils::Date date({1996, 11, 22});
frame[symbol] = TypedValue(date);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, LocalTime) {
const memgraph::utils::LocalTime lt({1, 2, 3, 11, 22});
frame[symbol] = TypedValue(lt);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, LocalDateTime) {
const memgraph::utils::LocalDateTime ldt({1993, 8, 6}, {2, 3, 4, 55, 40});
frame[symbol] = TypedValue(ldt);
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Null) {
frame[symbol] = TypedValue();
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsNull());
}
TEST_F(ExpressionEvaluatorAllPropertiesLookup, Map) {
frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}});
auto all_properties = Value();
EXPECT_TRUE(all_properties.IsMap());
}
class FunctionTest : public ExpressionEvaluatorTest {
protected:
std::vector<Expression *> ExpressionsFromTypedValues(const std::vector<TypedValue> &tvs) {

View File

@ -16,6 +16,7 @@
#include <sstream>
#include <tuple>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>
#include <variant>
@ -958,6 +959,20 @@ TYPED_TEST(TestPlanner, MapLiteralAggregationReturn) {
CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce());
}
TYPED_TEST(TestPlanner, MapProjectionLiteralAggregationReturn) {
// Test WITH {} as map RETURN map {sum: SUM(2)} AS result, 42 AS group_by
AstStorage storage;
FakeDbAccessor dba;
auto sum = SUM(LITERAL(2), false);
auto group_by_literal = LITERAL(42);
auto elements = std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{
{storage.GetPropertyIx("sum"), sum}};
auto *query = QUERY(SINGLE_QUERY(WITH(MAP(), AS("map")), RETURN(MAP_PROJECTION(IDENT("map"), elements), AS("result"),
group_by_literal, AS("group_by"))));
auto aggr = ExpectAggregate({sum}, {group_by_literal});
CheckPlan<TypeParam>(query, storage, ExpectProduce(), aggr, ExpectProduce());
}
TYPED_TEST(TestPlanner, EmptyListIndexAggregation) {
// Test RETURN [][SUM(2)] AS result, 42 AS group_by
AstStorage storage;
@ -1009,7 +1024,7 @@ TYPED_TEST(TestPlanner, AggregatonWithListWithAggregationAndGroupBy) {
}
TYPED_TEST(TestPlanner, MapWithAggregationAndGroupBy) {
// Test RETURN {lit: 42, sum: sum(2)}
// Test RETURN {lit: 42, sum: sum(2)} AS result
AstStorage storage;
FakeDbAccessor dba;
auto sum = SUM(LITERAL(2), false);
@ -1020,6 +1035,20 @@ TYPED_TEST(TestPlanner, MapWithAggregationAndGroupBy) {
CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce());
}
TYPED_TEST(TestPlanner, MapProjectionWithAggregationAndGroupBy) {
// Test WITH {} as map RETURN map {lit: 42, sum: SUM(2)} AS result
AstStorage storage;
FakeDbAccessor dba;
auto sum = SUM(LITERAL(2), false);
auto group_by_literal = LITERAL(42);
auto projection = std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{
{storage.GetPropertyIx("lit"), group_by_literal}, {storage.GetPropertyIx("sum"), sum}};
auto *query =
QUERY(SINGLE_QUERY(WITH(MAP(), AS("map")), RETURN(MAP_PROJECTION(IDENT("map"), projection), AS("result"))));
auto aggr = ExpectAggregate({sum}, {group_by_literal});
CheckPlan<TypeParam>(query, storage, ExpectProduce(), aggr, ExpectProduce());
}
TYPED_TEST(TestPlanner, AtomIndexedLabelProperty) {
// Test MATCH (n :label {property: 42, not_indexed: 0}) RETURN n
AstStorage storage;

View File

@ -9,6 +9,7 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <cstddef>
#include <vector>
#include <gmock/gmock.h>
@ -70,6 +71,16 @@ TEST_F(ExpressionPrettyPrinterTest, Literals) {
EXPECT_EQ(ToString(LITERAL(memgraph::storage::PropertyValue(tt_vec))),
"[DURATION(\"P0DT0H0M0.000001S\"), DURATION(\"P0DT0H0M-0.000002S\"), LOCALTIME(\"00:00:00.000002\"), "
"LOCALDATETIME(\"1970-01-01T00:00:00.000003\"), DATE(\"1970-01-01\")]");
// map {literalEntry: 10, variableSelector: a, .map, .*}
auto elements = std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{
{storage.GetPropertyIx("literalEntry"), LITERAL(10)},
{storage.GetPropertyIx("variableSelector"), IDENT("a")},
{storage.GetPropertyIx("propertySelector"), PROPERTY_LOOKUP("map", PROPERTY_PAIR("hello"))},
{storage.GetPropertyIx("allPropertiesSelector"), ALL_PROPERTIES_LOOKUP("map")}};
EXPECT_EQ(ToString(MAP_PROJECTION(IDENT("map"), elements)),
"(Identifier \"map\"){\"allPropertiesSelector\": .*, \"literalEntry\": 10, \"propertySelector\": "
"(PropertyLookup (Identifier \"map\") \"hello\"), \"variableSelector\": (Identifier \"a\")}");
}
TEST_F(ExpressionPrettyPrinterTest, Identifiers) {

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -194,6 +194,12 @@ TEST(QueryStripper, MapLiteral) {
EXPECT_EQ(stripped.query(), "MATCH ( n ) RETURN { val : n }");
}
TEST(QueryStripper, MapProjectionLiteral) {
StrippedQuery stripped("WITH 0 as var MATCH (n) RETURN n {.x, var, key: 'a'}");
EXPECT_EQ(stripped.literals().size(), 2);
EXPECT_EQ(stripped.query(), "WITH 0 as var MATCH ( n ) RETURN n { . x , var , key : \"a\" }");
}
TEST(QueryStripper, RangeLiteral) {
StrippedQuery stripped("MATCH (n)-[*2..3]-() RETURN n");
EXPECT_EQ(stripped.literals().size(), 2);
@ -357,6 +363,11 @@ TEST(QueryStripper, QueryReturnMap) {
EXPECT_THAT(stripped.named_expressions(), UnorderedElementsAre(Pair(1, "{a: 1, b: 'foo'}")));
}
TEST(QueryStripper, QueryReturnMapProjection) {
StrippedQuery stripped("RETURN a {.prop, var, key: 2}");
EXPECT_THAT(stripped.named_expressions(), UnorderedElementsAre(Pair(1, "a {.prop, var, key: 2}")));
}
TEST(QueryStripper, QuerySemicolonEndingQuery1) {
StrippedQuery stripped("RETURN 1;");
EXPECT_THAT(stripped.named_expressions(), UnorderedElementsAre(Pair(1, "1")));