Add support for map projection (#892)
This commit is contained in:
parent
802f8aceda
commit
0d9bd74a8a
@ -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};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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))));
|
||||
|
@ -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*>
|
||||
*/
|
||||
|
@ -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_); }
|
||||
|
@ -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
|
||||
|
@ -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 < = 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 < = 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.
|
||||
///
|
||||
|
@ -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{};
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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 |
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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")));
|
||||
|
Loading…
Reference in New Issue
Block a user