Add Property Map support in CREATE clause (#220)

This commit is contained in:
Jure Bajic 2021-09-09 12:39:13 +02:00 committed by GitHub
parent 2afc1b99f6
commit f560293657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 390 additions and 144 deletions

View File

@ -3,6 +3,7 @@
#include <memory>
#include <unordered_map>
#include <variant>
#include <vector>
#include "query/frontend/ast/ast_visitor.hpp"
@ -1237,6 +1238,19 @@ cpp<#
(:clone :ignore-other-base-classes t)
(:type-info :ignore-other-base-classes t))
(defun clone-variant-properties (source destination)
#>cpp
if (const auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&${source})) {
auto &new_obj_properties = std::get<std::unordered_map<PropertyIx, Expression *>>(${destination});
for (const auto &[property, value_expression] : *properties) {
PropertyIx key = storage->GetPropertyIx(property.name);
new_obj_properties[key] = value_expression->Clone(storage);
}
} else {
${destination} = std::get<ParameterLookup *>(${source})->Clone(storage);
}
cpp<#)
(lcp:define-class node-atom (pattern-atom)
((labels "std::vector<LabelIx>" :scope :public
:slk-load (lambda (member)
@ -1249,21 +1263,23 @@ cpp<#
}
cpp<#)
:clone (clone-name-ix-vector "Label"))
(properties "std::unordered_map<PropertyIx, Expression *>"
:slk-save #'slk-save-property-map
:slk-load #'slk-load-property-map
:clone #'clone-property-map
(properties "std::variant<std::unordered_map<PropertyIx, Expression *>, ParameterLookup*>"
:clone #'clone-variant-properties
:scope :public))
(:public
#>cpp
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
if (auto* properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&properties_)) {
bool cont = identifier_->Accept(visitor);
for (auto &property : properties_) {
for (auto &property : *properties) {
if (cont) {
cont = property.second->Accept(visitor);
}
}
} else {
std::get<ParameterLookup*>(properties_)->Accept(visitor);
}
}
return visitor.PostVisit(*this);
}
@ -1293,11 +1309,11 @@ cpp<#
}
cpp<#)
:clone (clone-name-ix-vector "EdgeType"))
(properties "std::unordered_map<PropertyIx, Expression *>"
(properties "std::variant<std::unordered_map<PropertyIx, Expression *>, ParameterLookup*>"
:scope :public
:slk-save #'slk-save-property-map
:slk-load #'slk-load-property-map
:clone #'clone-property-map)
:clone #'clone-variant-properties)
(lower-bound "Expression *" :initval "nullptr" :scope :public
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression")
@ -1349,11 +1365,15 @@ cpp<#
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
bool cont = identifier_->Accept(visitor);
for (auto &property : properties_) {
if (auto *properties = std::get_if<std::unordered_map<query::PropertyIx, query::Expression *>>(&properties_)) {
for (auto &property : *properties) {
if (cont) {
cont = property.second->Accept(visitor);
}
}
} else {
std::get<ParameterLookup *>(properties_)->Accept(visitor);
}
if (cont && lower_bound_) {
cont = lower_bound_->Accept(visitor);
}

View File

@ -7,6 +7,7 @@
// of the same name, EOF.
// This hides the definition of the macro which causes
// the compilation to fail.
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/procedure/module.hpp"
//////////////////////////////////////////////////////
#include "query/frontend/ast/cypher_main_visitor.hpp"
@ -21,6 +22,7 @@
#include <tuple>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#include "query/exceptions.hpp"
@ -1111,7 +1113,12 @@ antlrcpp::Any CypherMainVisitor::visitNodePattern(MemgraphCypher::NodePatternCon
node->labels_ = ctx->nodeLabels()->accept(this).as<std::vector<LabelIx>>();
}
if (ctx->properties()) {
// This can return either properties or parameters
if (ctx->properties()->mapLiteral()) {
node->properties_ = ctx->properties()->accept(this).as<std::unordered_map<PropertyIx, Expression *>>();
} else {
node->properties_ = ctx->properties()->accept(this).as<ParameterLookup *>();
}
}
return node;
}
@ -1125,15 +1132,12 @@ antlrcpp::Any CypherMainVisitor::visitNodeLabels(MemgraphCypher::NodeLabelsConte
}
antlrcpp::Any CypherMainVisitor::visitProperties(MemgraphCypher::PropertiesContext *ctx) {
if (!ctx->mapLiteral()) {
// If child is not mapLiteral that means child is params. At the moment
// we don't support properties to be a param because we can generate
// better logical plan if we have an information about properties at
// compile time.
// TODO: implement other clauses.
throw utils::NotYetImplemented("property parameters");
}
if (ctx->mapLiteral()) {
return ctx->mapLiteral()->accept(this);
}
// If child is not mapLiteral that means child is params.
MG_ASSERT(ctx->parameter());
return ctx->parameter()->accept(this);
}
antlrcpp::Any CypherMainVisitor::visitMapLiteral(MemgraphCypher::MapLiteralContext *ctx) {
@ -1332,9 +1336,14 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(MemgraphCypher::Relati
case 0:
break;
case 1: {
if (properties[0]->mapLiteral()) {
edge->properties_ = properties[0]->accept(this).as<std::unordered_map<PropertyIx, Expression *>>();
break;
}
MG_ASSERT(properties[0]->parameter());
edge->properties_ = properties[0]->accept(this).as<ParameterLookup *>();
break;
}
default:
throw SemanticException("Only one property map can be supplied for edge.");
}

View File

@ -6,7 +6,10 @@
#include <optional>
#include <unordered_set>
#include <variant>
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "utils/algorithm.hpp"
#include "utils/logging.hpp"
@ -402,19 +405,33 @@ bool SymbolGenerator::PostVisit(Pattern &) {
}
bool SymbolGenerator::PreVisit(NodeAtom &node_atom) {
scope_.in_node_atom = true;
bool props_or_labels = !node_atom.properties_.empty() || !node_atom.labels_.empty();
auto check_node_semantic = [&node_atom, this](const bool props_or_labels) {
const auto &node_name = node_atom.identifier_->name_;
if ((scope_.in_create || scope_.in_merge) && props_or_labels && HasSymbol(node_name)) {
throw SemanticException("Cannot create node '" + node_name +
"' with labels or properties, because it is already declared.");
}
for (auto kv : node_atom.properties_) {
kv.second->Accept(*this);
}
scope_.in_pattern_atom_identifier = true;
node_atom.identifier_->Accept(*this);
scope_.in_pattern_atom_identifier = false;
};
scope_.in_node_atom = true;
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&node_atom.properties_)) {
bool props_or_labels = !properties->empty() || !node_atom.labels_.empty();
check_node_semantic(props_or_labels);
for (auto kv : *properties) {
kv.second->Accept(*this);
}
return false;
}
auto &properties_parameter = std::get<ParameterLookup *>(node_atom.properties_);
bool props_or_labels = !properties_parameter || !node_atom.labels_.empty();
check_node_semantic(props_or_labels);
properties_parameter->Accept(*this);
return false;
}
@ -444,9 +461,13 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
"edge.");
}
}
for (auto kv : edge_atom.properties_) {
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&edge_atom.properties_)) {
for (auto kv : *properties) {
kv.second->Accept(*this);
}
} else {
std::get<ParameterLookup *>(edge_atom.properties_)->Accept(*this);
}
if (edge_atom.IsVariable()) {
scope_.in_edge_range = true;
if (edge_atom.lower_bound_) {

View File

@ -182,7 +182,18 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
storage::View::NEW);
// TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory
// when we update PropertyValue with custom allocator.
for (auto &kv : node_info.properties) PropsSetChecked(&new_node, kv.first, kv.second->Accept(evaluator));
if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) {
for (const auto &[key, value_expression] : *node_info_properties) {
PropsSetChecked(&new_node, key, value_expression->Accept(evaluator));
}
} else {
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties));
for (const auto &[key, value] : property_map.ValueMap()) {
auto property_id = dba.NameToProperty(key);
PropsSetChecked(&new_node, property_id, value);
}
}
(*frame)[node_info.symbol] = new_node;
return (*frame)[node_info.symbol].ValueVertex();
}
@ -255,7 +266,18 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert
auto maybe_edge = dba->InsertEdge(from, to, edge_info.edge_type);
if (maybe_edge.HasValue()) {
auto &edge = *maybe_edge;
for (auto kv : edge_info.properties) PropsSetChecked(&edge, kv.first, kv.second->Accept(*evaluator));
if (const auto *properties = std::get_if<PropertiesMapList>(&edge_info.properties)) {
for (const auto &[key, value_expression] : *properties) {
PropsSetChecked(&edge, key, value_expression->Accept(*evaluator));
}
} else {
auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties));
for (const auto &[key, value] : property_map.ValueMap()) {
auto property_id = dba->NameToProperty(key);
PropsSetChecked(&edge, property_id, value);
}
}
(*frame)[edge_info.symbol] = edge;
} else {
switch (maybe_edge.GetError()) {

View File

@ -8,6 +8,7 @@
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
#include "query/common.hpp"
@ -348,15 +349,54 @@ and false on every following Pull.")
}
cpp<#)
(defun clone-variant-properties (source destination)
#>cpp
if (const auto *props = std::get_if<PropertiesMapList>(&${source})) {
auto &destination_props = std::get<PropertiesMapList>(${destination});
destination_props.resize(props->size());
for (auto i0 = 0; i0 < props->size(); ++i0) {
{
storage::PropertyId first1 = (*props)[i0].first;
Expression *second2;
second2 = (*props)[i0].second ? (*props)[i0].second->Clone(storage) : nullptr;
destination_props[i0] = std::make_pair(std::move(first1), std::move(second2));
}
}
} else {
${destination} = std::get<ParameterLookup *>(${source})->Clone(storage);
}
cpp<#)
#>cpp
using PropertiesMapList = std::vector<std::pair<storage::PropertyId, Expression *>>;
cpp<#
(lcp:define-struct node-creation-info ()
((symbol "Symbol")
(labels "std::vector<storage::LabelId>")
(properties "std::vector<std::pair<storage::PropertyId, Expression *>>"
(properties "std::variant<PropertiesMapList, ParameterLookup *>"
:slk-save #'slk-save-properties
:slk-load #'slk-load-properties))
:slk-load #'slk-load-properties
:clone #'clone-variant-properties))
(:serialize (:slk :save-args '((helper "query::plan::LogicalOperator::SaveHelper *"))
:load-args '((helper "query::plan::LogicalOperator::SlkLoadHelper *"))))
(:clone :args '((storage "AstStorage *"))))
(:clone :args '((storage "AstStorage *")))
(:public
#>cpp
NodeCreationInfo() = default;
NodeCreationInfo(
Symbol symbol, std::vector<storage::LabelId> labels,
std::variant<PropertiesMapList, ParameterLookup *> properties)
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {};
NodeCreationInfo(Symbol symbol, std::vector<storage::LabelId> labels,
PropertiesMapList properties)
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{std::move(properties)} {};
NodeCreationInfo(Symbol symbol, std::vector<storage::LabelId> labels, ParameterLookup* properties)
: symbol{std::move(symbol)}, labels{std::move(labels)}, properties{properties} {};
cpp<#))
(lcp:define-class create-node (logical-operator)
((input "std::shared_ptr<LogicalOperator>" :scope :public
@ -375,7 +415,7 @@ and false on every following Pull.")
"Operator for creating a node.
This op is used both for creating a single node (`CREATE` statement without
a preceeding `MATCH`), or multiple nodes (`MATCH ... CREATE` or
a preceding `MATCH`), or multiple nodes (`MATCH ... CREATE` or
`CREATE (), () ...`).
@sa CreateExpand")
@ -421,14 +461,31 @@ a preceeding `MATCH`), or multiple nodes (`MATCH ... CREATE` or
(lcp:define-struct edge-creation-info ()
((symbol "Symbol")
(properties "std::vector<std::pair<storage::PropertyId, Expression *>>"
(properties "std::variant<PropertiesMapList, ParameterLookup *>"
:slk-save #'slk-save-properties
:slk-load #'slk-load-properties)
:slk-load #'slk-load-properties
:clone #'clone-variant-properties)
(edge-type "::storage::EdgeTypeId")
(direction "::EdgeAtom::Direction" :initval "EdgeAtom::Direction::BOTH"))
(:serialize (:slk :save-args '((helper "query::plan::LogicalOperator::SaveHelper *"))
:load-args '((helper "query::plan::LogicalOperator::SlkLoadHelper *"))))
(:clone :args '((storage "AstStorage *"))))
(:clone :args '((storage "AstStorage *")))
(:public
#>cpp
EdgeCreationInfo() = default;
EdgeCreationInfo(Symbol symbol, std::variant<PropertiesMapList, ParameterLookup *> properties,
storage::EdgeTypeId edge_type, EdgeAtom::Direction direction)
: symbol{std::move(symbol)}, properties{std::move(properties)}, edge_type{edge_type}, direction{direction} {};
EdgeCreationInfo(Symbol symbol, PropertiesMapList properties,
storage::EdgeTypeId edge_type, EdgeAtom::Direction direction)
: symbol{std::move(symbol)}, properties{std::move(properties)}, edge_type{edge_type}, direction{direction} {};
EdgeCreationInfo(Symbol symbol, ParameterLookup* properties,
storage::EdgeTypeId edge_type, EdgeAtom::Direction direction)
: symbol{std::move(symbol)}, properties{properties}, edge_type{edge_type}, direction{direction} {};
cpp<#))
(lcp:define-class create-expand (logical-operator)
((node-info "NodeCreationInfo" :scope :public

View File

@ -1,8 +1,13 @@
#include "query/plan/preprocess.hpp"
#include <algorithm>
#include <functional>
#include <stack>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/plan/preprocess.hpp"
namespace query::plan {
@ -218,7 +223,8 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
UsedSymbolsCollector collector(symbol_table);
auto add_properties_variable = [&](EdgeAtom *atom) {
const auto &symbol = symbol_table.at(*atom->identifier_);
for (auto &prop_pair : atom->properties_) {
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&atom->properties_)) {
for (auto &prop_pair : *properties) {
// We need to store two property-lookup filters in all_filters. One is
// used for inlining property filters into variable expansion, and
// utilizes the inner_edge symbol. The other is used for post-expansion
@ -247,15 +253,20 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
auto *property_lookup = storage.Create<PropertyLookup>(identifier, prop_pair.first);
auto *prop_equal = storage.Create<EqualOperator>(property_lookup, prop_pair.second);
// Currently, variable expand has no gains if we set PropertyFilter.
all_filters_.emplace_back(FilterInfo{
FilterInfo::Type::Generic,
storage.Create<All>(identifier, atom->identifier_, storage.Create<Where>(prop_equal)), collector.symbols_});
all_filters_.emplace_back(
FilterInfo{FilterInfo::Type::Generic,
storage.Create<All>(identifier, atom->identifier_, storage.Create<Where>(prop_equal)),
collector.symbols_});
}
}
return;
}
throw SemanticException("Property map matching not supported in MATCH/MERGE clause!");
};
auto add_properties = [&](auto *atom) {
const auto &symbol = symbol_table.at(*atom->identifier_);
for (auto &prop_pair : atom->properties_) {
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&atom->properties_)) {
for (auto &prop_pair : *properties) {
// Create an equality expression and store it in all_filters_.
auto *property_lookup = storage.Create<PropertyLookup>(atom->identifier_, prop_pair.first);
auto *prop_equal = storage.Create<EqualOperator>(property_lookup, prop_pair.second);
@ -267,6 +278,9 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
PropertyFilter::Type::EQUAL);
all_filters_.emplace_back(filter_info);
}
return;
}
throw SemanticException("Property map matching not supported in MATCH/MERGE clause!");
};
auto add_node_filter = [&](NodeAtom *node) {
const auto &node_symbol = symbol_table.at(*node->identifier_);

View File

@ -1,4 +1,5 @@
#include "query/plan/pretty_print.hpp"
#include <variant>
#include "query/db_accessor.hpp"
#include "query/frontend/ast/pretty_print.hpp"
@ -353,14 +354,16 @@ json ToJson(const NodeCreationInfo &node_info, const DbAccessor &dba) {
json self;
self["symbol"] = ToJson(node_info.symbol);
self["labels"] = ToJson(node_info.labels, dba);
self["properties"] = ToJson(node_info.properties, dba);
const auto *props = std::get_if<PropertiesMapList>(&node_info.properties);
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, dba);
return self;
}
json ToJson(const EdgeCreationInfo &edge_info, const DbAccessor &dba) {
json self;
self["symbol"] = ToJson(edge_info.symbol);
self["properties"] = ToJson(edge_info.properties, dba);
const auto *props = std::get_if<PropertiesMapList>(&edge_info.properties);
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, dba);
self["edge_type"] = ToJson(edge_info.edge_type, dba);
self["direction"] = ToString(edge_info.direction);
return self;

View File

@ -2,10 +2,12 @@
#pragma once
#include <optional>
#include <variant>
#include "gflags/gflags.h"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/plan/operator.hpp"
#include "query/plan/preprocess.hpp"
#include "utils/logging.hpp"
@ -247,11 +249,19 @@ class RuleBasedPlanner {
for (const auto &label : node.labels_) {
labels.push_back(GetLabel(label));
}
std::vector<std::pair<storage::PropertyId, Expression *>> properties;
properties.reserve(node.properties_.size());
for (const auto &kv : node.properties_) {
properties.push_back({GetProperty(kv.first), kv.second});
auto properties = std::invoke([&]() -> std::variant<PropertiesMapList, ParameterLookup *> {
if (const auto *node_properties =
std::get_if<std::unordered_map<PropertyIx, Expression *>>(&node.properties_)) {
PropertiesMapList vector_props;
vector_props.reserve(node_properties->size());
for (const auto &kv : *node_properties) {
vector_props.push_back({GetProperty(kv.first), kv.second});
}
return std::move(vector_props);
}
return std::get<ParameterLookup *>(node.properties_);
});
return NodeCreationInfo{node_symbol, labels, properties};
};
@ -260,9 +270,8 @@ class RuleBasedPlanner {
if (bound_symbols.insert(node_symbol).second) {
auto node_info = node_to_creation_info(*node);
return std::make_unique<CreateNode>(std::move(input_op), node_info);
} else {
return std::move(input_op);
}
return std::move(input_op);
};
auto collect = [&](std::unique_ptr<LogicalOperator> last_op, NodeAtom *prev_node, EdgeAtom *edge, NodeAtom *node) {
@ -279,11 +288,19 @@ class RuleBasedPlanner {
LOG_FATAL("Symbols used for created edges cannot be redeclared.");
}
auto node_info = node_to_creation_info(*node);
std::vector<std::pair<storage::PropertyId, Expression *>> properties;
properties.reserve(edge->properties_.size());
for (const auto &kv : edge->properties_) {
properties.push_back({GetProperty(kv.first), kv.second});
auto properties = std::invoke([&]() -> std::variant<PropertiesMapList, ParameterLookup *> {
if (const auto *edge_properties =
std::get_if<std::unordered_map<PropertyIx, Expression *>>(&edge->properties_)) {
PropertiesMapList vector_props;
vector_props.reserve(edge_properties->size());
for (const auto &kv : *edge_properties) {
vector_props.push_back({GetProperty(kv.first), kv.second});
}
return std::move(vector_props);
}
return std::get<ParameterLookup *>(edge->properties_);
});
MG_ASSERT(edge->edge_types_.size() == 1, "Creating an edge with a single type should be required by syntax");
EdgeCreationInfo edge_info{edge_symbol, properties, GetEdgeType(edge->edge_types_[0]), edge->direction_};
return std::make_unique<CreateExpand>(node_info, edge_info, std::move(last_op), input_symbol, node_existing);

View File

@ -1,6 +1,6 @@
#include <string>
#include <benchmark/benchmark_api.h>
#include <string>
#include <variant>
#include "query/frontend/semantic/symbol_generator.hpp"
#include "query/plan/cost_estimator.hpp"
@ -70,7 +70,7 @@ static query::CypherQuery *AddIndexedMatches(int num_matches, const std::string
std::string node1_name = "node" + std::to_string(i - 1);
auto *node = storage.Create<query::NodeAtom>(storage.Create<query::Identifier>(node1_name));
node->labels_.emplace_back(storage.GetLabelIx(label));
node->properties_[storage.GetPropertyIx(property)] = storage.Create<query::PrimitiveLiteral>(i);
std::get<0>(node->properties_)[storage.GetPropertyIx(property)] = storage.Create<query::PrimitiveLiteral>(i);
pattern->atoms_.emplace_back(node);
single_query->clauses_.emplace_back(match);
query->single_query_ = single_query;

View File

@ -3,6 +3,7 @@
#include <limits>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
//////////////////////////////////////////////////////
@ -914,7 +915,7 @@ TEST_P(CypherMainVisitorTest, NodePattern) {
EXPECT_THAT(node->labels_, UnorderedElementsAre(ast_generator.Label("label1"), ast_generator.Label("label2"),
ast_generator.Label("label3")));
std::unordered_map<PropertyIx, int64_t> properties;
for (auto x : node->properties_) {
for (auto x : std::get<0>(node->properties_)) {
TypedValue value = ast_generator.LiteralValue(x.second);
ASSERT_TRUE(value.type() == TypedValue::Type::Int);
properties[x.first] = value.ValueInt();
@ -943,7 +944,7 @@ TEST_P(CypherMainVisitorTest, NodePatternIdentifier) {
EXPECT_EQ(node->identifier_->name_, "var");
EXPECT_TRUE(node->identifier_->user_declared_);
EXPECT_THAT(node->labels_, UnorderedElementsAre());
EXPECT_THAT(node->properties_, UnorderedElementsAre());
EXPECT_THAT(std::get<0>(node->properties_), UnorderedElementsAre());
}
TEST_P(CypherMainVisitorTest, RelationshipPatternNoDetails) {
@ -1029,7 +1030,7 @@ TEST_P(CypherMainVisitorTest, RelationshipPatternDetails) {
EXPECT_THAT(edge->edge_types_,
UnorderedElementsAre(ast_generator.EdgeType("type1"), ast_generator.EdgeType("type2")));
std::unordered_map<PropertyIx, int64_t> properties;
for (auto x : edge->properties_) {
for (auto x : std::get<0>(edge->properties_)) {
TypedValue value = ast_generator.LiteralValue(x.second);
ASSERT_TRUE(value.type() == TypedValue::Type::Int);
properties[x.first] = value.ValueInt();
@ -1169,7 +1170,7 @@ TEST_P(CypherMainVisitorTest, RelationshipPatternUnboundedWithProperty) {
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
ast_generator.CheckLiteral(edge->properties_[ast_generator.Prop("prop")], 42);
ast_generator.CheckLiteral(std::get<0>(edge->properties_)[ast_generator.Prop("prop")], 42);
}
TEST_P(CypherMainVisitorTest, RelationshipPatternDotsUnboundedWithEdgeTypeProperty) {
@ -1186,7 +1187,7 @@ TEST_P(CypherMainVisitorTest, RelationshipPatternDotsUnboundedWithEdgeTypeProper
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
ast_generator.CheckLiteral(edge->properties_[ast_generator.Prop("prop")], 42);
ast_generator.CheckLiteral(std::get<0>(edge->properties_)[ast_generator.Prop("prop")], 42);
ASSERT_EQ(edge->edge_types_.size(), 1U);
auto edge_type = ast_generator.EdgeType("edge_type");
EXPECT_EQ(edge->edge_types_[0], edge_type);
@ -1205,7 +1206,7 @@ TEST_P(CypherMainVisitorTest, RelationshipPatternUpperBoundedWithProperty) {
EXPECT_EQ(edge->type_, EdgeAtom::Type::DEPTH_FIRST);
EXPECT_EQ(edge->lower_bound_, nullptr);
ast_generator.CheckLiteral(edge->upper_bound_, 2);
ast_generator.CheckLiteral(edge->properties_[ast_generator.Prop("prop")], 42);
ast_generator.CheckLiteral(std::get<0>(edge->properties_)[ast_generator.Prop("prop")], 42);
}
// TODO maybe uncomment

View File

@ -224,6 +224,77 @@ TEST_F(InterpreterTest, Parameters) {
}
}
// Run CREATE/MATCH/MERGE queries with property map
TEST_F(InterpreterTest, ParametersAsPropertyMap) {
{
std::map<std::string, storage::PropertyValue> property_map{};
property_map["name"] = storage::PropertyValue("name1");
property_map["age"] = storage::PropertyValue(25);
auto stream = Interpret("CREATE (n $prop) RETURN n", {
{"prop", storage::PropertyValue(property_map)},
});
ASSERT_EQ(stream.GetHeader().size(), 1U);
ASSERT_EQ(stream.GetHeader()[0], "n");
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
auto result = stream.GetResults()[0][0].ValueVertex();
EXPECT_EQ(result.properties["name"].ValueString(), "name1");
EXPECT_EQ(result.properties["age"].ValueInt(), 25);
}
{
std::map<std::string, storage::PropertyValue> property_map{};
property_map["name"] = storage::PropertyValue("name1");
property_map["age"] = storage::PropertyValue(25);
Interpret("CREATE (:Person)");
auto stream =
Interpret("MATCH (m: Person) CREATE (n $prop) RETURN n", {
{"prop", storage::PropertyValue(property_map)},
});
ASSERT_EQ(stream.GetHeader().size(), 1U);
ASSERT_EQ(stream.GetHeader()[0], "n");
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
auto result = stream.GetResults()[0][0].ValueVertex();
EXPECT_EQ(result.properties["name"].ValueString(), "name1");
EXPECT_EQ(result.properties["age"].ValueInt(), 25);
}
{
std::map<std::string, storage::PropertyValue> property_map{};
property_map["name"] = storage::PropertyValue("name1");
property_map["weight"] = storage::PropertyValue(121);
auto stream = Interpret("CREATE ()-[r:TO $prop]->() RETURN r", {
{"prop", storage::PropertyValue(property_map)},
});
ASSERT_EQ(stream.GetHeader().size(), 1U);
ASSERT_EQ(stream.GetHeader()[0], "r");
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
auto result = stream.GetResults()[0][0].ValueEdge();
EXPECT_EQ(result.properties["name"].ValueString(), "name1");
EXPECT_EQ(result.properties["weight"].ValueInt(), 121);
}
{
std::map<std::string, storage::PropertyValue> property_map{};
property_map["name"] = storage::PropertyValue("name1");
property_map["age"] = storage::PropertyValue(15);
ASSERT_THROW(Interpret("MATCH (n $prop) RETURN n",
{
{"prop", storage::PropertyValue(property_map)},
}),
query::SemanticException);
}
{
std::map<std::string, storage::PropertyValue> property_map{};
property_map["name"] = storage::PropertyValue("name1");
property_map["age"] = storage::PropertyValue(15);
ASSERT_THROW(Interpret("MERGE (n $prop) RETURN n",
{
{"prop", storage::PropertyValue(property_map)},
}),
query::SemanticException);
}
}
// Test bfs end to end.
TEST_F(InterpreterTest, Bfs) {
srand(0);

View File

@ -6,6 +6,7 @@
#include <tuple>
#include <typeinfo>
#include <unordered_set>
#include <variant>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@ -617,10 +618,10 @@ TYPED_TEST(TestPlanner, MatchCrossReferenceVariable) {
AstStorage storage;
auto node_n = NODE("n");
auto m_prop = PROPERTY_LOOKUP("m", prop.second);
node_n->properties_[storage.GetPropertyIx(prop.first)] = m_prop;
std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = m_prop;
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_m->properties_[storage.GetPropertyIx(prop.first)] = n_prop;
std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop;
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN("n")));
// We expect both ScanAll to come before filters (2 are joined into one),
// because they need to populate the symbol values.
@ -765,7 +766,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) {
AstStorage storage;
FakeDbAccessor dba;
auto node_n = NODE("n");
node_n->properties_[storage.GetPropertyIx("prop")] = IDENT("i");
std::get<0>(node_n->properties_)[storage.GetPropertyIx("prop")] = IDENT("i");
auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n))));
std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()};
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
@ -783,7 +784,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) {
const auto property = PROPERTY_PAIR("prop");
dba.SetIndexCount(label, property.second, 1);
auto node_n = NODE("n", label_name);
node_n->properties_[storage.GetPropertyIx(property.first)] = IDENT("i");
std::get<0>(node_n->properties_)[storage.GetPropertyIx(property.first)] = IDENT("i");
auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n))));
std::list<BaseOpChecker *> on_match{new ExpectScanAllByLabelPropertyValue(label, property, IDENT("i"))};
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
@ -916,8 +917,8 @@ TYPED_TEST(TestPlanner, AtomIndexedLabelProperty) {
dba.SetIndexCount(label, property.second, 1);
auto node = NODE("n", "label");
auto lit_42 = LITERAL(42);
node->properties_[storage.GetPropertyIx(property.first)] = lit_42;
node->properties_[storage.GetPropertyIx(not_indexed.first)] = LITERAL(0);
std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42;
std::get<0>(node->properties_)[storage.GetPropertyIx(not_indexed.first)] = LITERAL(0);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN("n")));
auto symbol_table = query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
@ -935,7 +936,7 @@ TYPED_TEST(TestPlanner, AtomPropertyWhereLabelIndexing) {
dba.SetIndexCount(label, property.second, 0);
auto node = NODE("n");
auto lit_42 = LITERAL(42);
node->properties_[storage.GetPropertyIx(property.first)] = lit_42;
std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42;
auto *query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node)),
WHERE(AND(PROPERTY_LOOKUP("n", not_indexed),
@ -1141,7 +1142,7 @@ TYPED_TEST(TestPlanner, MatchExpandVariableInlinedFilter) {
auto prop = PROPERTY_PAIR("prop");
AstStorage storage;
auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type});
edge->properties_[storage.GetPropertyIx(prop.first)] = LITERAL(42);
std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(),
ExpectExpandVariable(), // Filter is both inlined and post-expand
@ -1155,7 +1156,7 @@ TYPED_TEST(TestPlanner, MatchExpandVariableNotInlinedFilter) {
auto prop = PROPERTY_PAIR("prop");
AstStorage storage;
auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type});
edge->properties_[storage.GetPropertyIx(prop.first)] = EQ(PROPERTY_LOOKUP("m", prop), LITERAL(42));
std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = EQ(PROPERTY_LOOKUP("m", prop), LITERAL(42));
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectFilter(), ExpectProduce());
}
@ -1241,7 +1242,7 @@ TYPED_TEST(TestPlanner, MatchScanToExpand) {
dba.SetIndexCount(label, FLAGS_query_vertex_count_to_expand_existing + 1);
AstStorage storage;
auto node_m = NODE("m", "label");
node_m->properties_[storage.GetPropertyIx("property")] = LITERAL(1);
std::get<0>(node_m->properties_)[storage.GetPropertyIx("property")] = LITERAL(1);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), node_m)), RETURN("r")));
auto symbol_table = query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);

View File

@ -1,5 +1,6 @@
#include <iterator>
#include <memory>
#include <variant>
#include <vector>
#include "gmock/gmock.h"
@ -29,7 +30,8 @@ TEST(QueryPlan, CreateNodeWithAttributes) {
NodeCreationInfo node;
node.symbol = symbol_table.CreateSymbol("n", true);
node.labels.emplace_back(label);
node.properties.emplace_back(property.second, LITERAL(42));
std::get<std::vector<std::pair<storage::PropertyId, Expression *>>>(node.properties)
.emplace_back(property.second, LITERAL(42));
auto create = std::make_shared<CreateNode>(nullptr, node);
auto context = MakeContext(storage, symbol_table, &dba);
@ -73,7 +75,8 @@ TEST(QueryPlan, CreateReturn) {
NodeCreationInfo node;
node.symbol = symbol_table.CreateSymbol("n", true);
node.labels.emplace_back(label);
node.properties.emplace_back(property.second, LITERAL(42));
std::get<std::vector<std::pair<storage::PropertyId, Expression *>>>(node.properties)
.emplace_back(property.second, LITERAL(42));
auto create = std::make_shared<CreateNode>(nullptr, node);
auto named_expr_n =
@ -118,18 +121,20 @@ TEST(QueryPlan, CreateExpand) {
NodeCreationInfo n;
n.symbol = symbol_table.CreateSymbol("n", true);
n.labels.emplace_back(label_node_1);
n.properties.emplace_back(property.second, LITERAL(1));
std::get<std::vector<std::pair<storage::PropertyId, Expression *>>>(n.properties)
.emplace_back(property.second, LITERAL(1));
// data for the second node
NodeCreationInfo m;
m.symbol = cycle ? n.symbol : symbol_table.CreateSymbol("m", true);
m.labels.emplace_back(label_node_2);
m.properties.emplace_back(property.second, LITERAL(2));
std::get<std::vector<std::pair<storage::PropertyId, Expression *>>>(m.properties)
.emplace_back(property.second, LITERAL(2));
EdgeCreationInfo r;
r.symbol = symbol_table.CreateSymbol("r", true);
r.edge_type = edge_type;
r.properties.emplace_back(property.second, LITERAL(3));
std::get<0>(r.properties).emplace_back(property.second, LITERAL(3));
auto create_op = std::make_shared<CreateNode>(nullptr, n);
auto create_expand = std::make_shared<CreateExpand>(m, r, create_op, n.symbol, cycle);
@ -717,7 +722,7 @@ TEST(QueryPlan, NodeFilterSet) {
SymbolTable symbol_table;
// MATCH (n {prop: 42}) -[r]- (m)
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->properties_[storage.GetPropertyIx(prop.first)] = LITERAL(42);
std::get<0>(scan_all.node_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42);
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m",
false, storage::View::OLD);
auto *filter_expr =
@ -755,7 +760,7 @@ TEST(QueryPlan, FilterRemove) {
SymbolTable symbol_table;
// MATCH (n) -[r]- (m) WHERE n.prop < 43
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->properties_[storage.GetPropertyIx(prop.first)] = LITERAL(42);
std::get<0>(scan_all.node_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42);
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m",
false, storage::View::OLD);
auto filter_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop);

View File

@ -2,6 +2,7 @@
#include <memory>
#include <optional>
#include <unordered_map>
#include <variant>
#include <vector>
#include <fmt/format.h>
@ -165,7 +166,7 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) {
// make a scan all
auto n = MakeScanAll(storage, symbol_table, "n");
n.node_->labels_.emplace_back(storage.GetLabelIx(dba.LabelToName(label)));
n.node_->properties_[storage.GetPropertyIx(property.first)] = LITERAL(42);
std::get<0>(n.node_->properties_)[storage.GetPropertyIx(property.first)] = LITERAL(42);
// node filtering
auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_),
@ -1449,7 +1450,7 @@ TEST(QueryPlan, EdgeFilter) {
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {edge_type}, "m", false,
storage::View::OLD);
r_m.edge_->edge_types_.push_back(storage.GetEdgeTypeIx(dba.EdgeTypeToName(edge_type)));
r_m.edge_->properties_[storage.GetPropertyIx(prop.first)] = LITERAL(42);
std::get<0>(r_m.edge_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42);
auto *filter_expr = EQ(PROPERTY_LOOKUP(r_m.edge_->identifier_, prop), LITERAL(42));
auto edge_filter = std::make_shared<Filter>(r_m.op_, filter_expr);

View File

@ -19,7 +19,8 @@ TEST(QueryPlan, CreateNodeWithAttributes) {
query::plan::NodeCreationInfo node;
node.symbol = symbol_table.CreateSymbol("n", true);
node.labels.emplace_back(label);
node.properties.emplace_back(property, ast.Create<PrimitiveLiteral>(42));
std::get<std::vector<std::pair<storage::PropertyId, Expression *>>>(node.properties)
.emplace_back(property, ast.Create<PrimitiveLiteral>(42));
query::plan::CreateNode create_node(nullptr, node);
DbAccessor execution_dba(&dba);

View File

@ -1,5 +1,6 @@
#include <memory>
#include <sstream>
#include <variant>
#include "gtest/gtest.h"
@ -75,8 +76,8 @@ TEST_F(TestSymbolGenerator, MatchNodeUnboundReturn) {
TEST_F(TestSymbolGenerator, CreatePropertyUnbound) {
// AST with unbound variable in create: CREATE ({prop: x})
auto node = NODE("anon");
node->properties_[storage.GetPropertyIx("prop")] = IDENT("x");
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(node))));
std::get<0>(node->properties_)[storage.GetPropertyIx("prop")] = IDENT("x");
auto *query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(node))));
EXPECT_THROW(query::MakeSymbolTable(query_ast), UnboundVariableError);
}
@ -292,7 +293,7 @@ TEST_F(TestSymbolGenerator, CreateExpandProperty) {
// Test CREATE (n) -[r :r]-> (n {prop: 42})
auto r_type = "r";
auto n_prop = NODE("n");
n_prop->properties_[storage.GetPropertyIx("prop")] = LITERAL(42);
std::get<0>(n_prop->properties_)[storage.GetPropertyIx("prop")] = LITERAL(42);
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop))));
EXPECT_THROW(query::MakeSymbolTable(query), SemanticException);
}
@ -337,7 +338,7 @@ TEST_F(TestSymbolGenerator, MatchPropCreateNodeProp) {
auto node_n = NODE("n");
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_m->properties_[storage.GetPropertyIx(prop.first)] = n_prop;
std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m))));
auto symbol_table = query::MakeSymbolTable(query);
// symbols: pattern * 2, `node_n`, `node_m`
@ -552,10 +553,10 @@ TEST_F(TestSymbolGenerator, MatchCrossReferenceVariable) {
auto prop = PROPERTY_PAIR("prop");
auto node_n = NODE("n");
auto m_prop = PROPERTY_LOOKUP("m", prop.second);
node_n->properties_[storage.GetPropertyIx(prop.first)] = m_prop;
std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = m_prop;
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_m->properties_[storage.GetPropertyIx(prop.first)] = n_prop;
std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop;
auto ident_n = IDENT("n");
auto as_n = AS("n");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN(ident_n, as_n)));
@ -624,7 +625,7 @@ TEST_F(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) {
auto prop = PROPERTY_PAIR("prop");
auto edge = EDGE("r");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
edge->properties_[storage.GetPropertyIx(prop.first)] = n_prop;
std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = n_prop;
auto node_n = NODE("n");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r")));
auto symbol_table = query::MakeSymbolTable(query);
@ -707,7 +708,7 @@ TEST_F(TestSymbolGenerator, MatchPropertySameIdentifier) {
auto prop = PROPERTY_PAIR("prop");
auto node_n = NODE("n");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_n->properties_[storage.GetPropertyIx(prop.first)] = n_prop;
std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = n_prop;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), RETURN("n")));
auto symbol_table = query::MakeSymbolTable(query);
auto n = symbol_table.at(*node_n->identifier_);
@ -1140,7 +1141,8 @@ TEST_F(TestSymbolGenerator, PredefinedIdentifiers) {
// UNWIND first_op as u CREATE(first_op {prop: u})
auto unwind = UNWIND(first_op, AS("u"));
auto node = NODE("first_op");
node->properties_[storage.GetPropertyIx("prop")] = dynamic_cast<Identifier *>(unwind->named_expression_->expression_);
std::get<0>(node->properties_)[storage.GetPropertyIx("prop")] =
dynamic_cast<Identifier *>(unwind->named_expression_->expression_);
query = QUERY(SINGLE_QUERY(unwind, CREATE(PATTERN(node))));
ASSERT_THROW(query::MakeSymbolTable(query, {first_op}), SemanticException);
}

View File

@ -1,4 +1,5 @@
#include <algorithm>
#include <variant>
#include "gtest/gtest.h"
@ -277,7 +278,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
AstStorage storage;
auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH);
auto node_n = NODE("n");
node_n->properties_[storage.GetPropertyIx("id")] = LITERAL(1);
std::get<0>(node_n->properties_)[storage.GetPropertyIx("id")] = LITERAL(1);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)}); // [r1]