diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index 1f95ad948..36c88dad1 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -1105,6 +1105,8 @@ class MapProjectionLiteral : public memgraph::query::BaseLiteral { DEFVISITABLE(ExpressionVisitor<void>); bool Accept(HierarchicalTreeVisitor &visitor) override { if (visitor.PreVisit(*this)) { + map_variable_->Accept(visitor); + for (auto pair : elements_) { if (!pair.second) continue; diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp index 1c78d6c9b..39e6a7c5f 100644 --- a/src/query/interpret/eval.hpp +++ b/src/query/interpret/eval.hpp @@ -763,20 +763,27 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { TypedValue::TMap result(ctx_->memory); TypedValue::TMap all_properties_lookup(ctx_->memory); + + auto map_variable = literal.map_variable_->Accept(*this); + if (map_variable.IsNull()) { + return TypedValue(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()); + LOG_FATAL("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); diff --git a/src/query/plan/rule_based_planner.cpp b/src/query/plan/rule_based_planner.cpp index 65e4361e1..c02a7eaa6 100644 --- a/src/query/plan/rule_based_planner.cpp +++ b/src/query/plan/rule_based_planner.cpp @@ -98,6 +98,11 @@ class ReturnBodyContext : public HierarchicalTreeVisitor { auto it = has_aggregation_.end(); auto elements_it = literal.elements_.begin(); std::advance(it, -literal.elements_.size()); + if (literal.GetTypeInfo() == MapProjectionLiteral::kType) { + // Erase the map variable. Grammar-wise, it’s a variable and thus never has aggregations. + std::advance(it, -1); + it = has_aggregation_.erase(it); + } while (it != has_aggregation_.end()) { if (*it) { has_aggr = true; @@ -446,9 +451,9 @@ class ReturnBodyContext : public HierarchicalTreeVisitor { std::vector<Expression *> group_by_; std::unordered_set<Symbol> group_by_used_symbols_; // Flag stack indicating whether an expression contains an aggregation. A - // stack is needed so that we differentiate the case where a child - // sub-expression has an aggregation, while the other child doesn't. For - // example AST, (+ (sum x) y) + // stack is needed to address the case where one child sub-expression has + // an aggregation, while the other child does not. + // For example, the AST (+ (sum x) y) is as follows: // * (sum x) -- Has an aggregation. // * y -- Doesn't, we need to group by this. // * (+ (sum x) y) -- The whole expression has an aggregation, so we don't diff --git a/tests/gql_behave/tests/memgraph_V1/features/map_projection.feature b/tests/gql_behave/tests/memgraph_V1/features/map_projection.feature index f04b04a6e..da20f2856 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/map_projection.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/map_projection.feature @@ -4,7 +4,7 @@ Feature: Map projection When executing query: """ WITH {} AS map - RETURN map {} as result + RETURN map {} AS result """ Then the result should be: | result | @@ -26,6 +26,17 @@ Feature: Map projection | result | | {age: 85, lastName: 'Freeman', name: 'Morgan', oscars: 1} | + Scenario: Projecting from a null value + When executing query: + """ + WITH "value" AS var + OPTIONAL MATCH (n:Nonexistent) + RETURN n {.*} AS result0, n {.prop} AS result1, n {prop: "value"} AS result2, n {var} AS result3; + """ + Then the result should be: + | result0 | result1 | result2 | result3 | + | null | null | null | null | + Scenario: Projecting a nonexistent property When executing query: """