Map type now supported
Summary: - MapLiteral added - PropertyLookup on maps added This is the basic implementation, missing are: - unit tests - feature and TCK tests - documentation - changelog That stuff is coming. Please review the implementation (Mislav). Reviewers: mislav.bradac, buda, teon.banek Reviewed By: mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D640
This commit is contained in:
parent
bfe9ec0ab5
commit
b8957c999d
@ -5,6 +5,7 @@
|
||||
### Major Features and Improvements
|
||||
|
||||
* Support for variable length path `MATCH`.
|
||||
* Support for map literal.
|
||||
* Support for `all` function in openCypher.
|
||||
* User specified transaction execution timeout.
|
||||
* Support for query parameters (except for parameters in place of property maps).
|
||||
|
@ -34,3 +34,11 @@ types. Following is a table of supported data types.
|
||||
`Integer` | An integer number.
|
||||
`Float` | A floating-point number, i.e. a real number.
|
||||
`List` | A list containing any number of property values of any supported type. It can be used to store multiple values under a single property name.
|
||||
|
||||
Note that even though map literals are supported in openCypher queries, they can't be stored in the graph. For example, the following query is legal:
|
||||
|
||||
MATCH (n:Person) RETURN {name: n.name, age: n.age}
|
||||
|
||||
However, the next query is not:
|
||||
|
||||
MATCH (n:Person {name: "John"}) SET n.address = {city: "London", street: "Fleet St."}
|
||||
|
@ -580,6 +580,38 @@ class ListLiteral : public BaseLiteral {
|
||||
: BaseLiteral(uid), elements_(elements) {}
|
||||
};
|
||||
|
||||
class MapLiteral : public BaseLiteral {
|
||||
friend class AstTreeStorage;
|
||||
|
||||
public:
|
||||
DEFVISITABLE(TreeVisitor<TypedValue>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
for (auto pair : elements_)
|
||||
if (!pair.second->Accept(visitor)) break;
|
||||
}
|
||||
return visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
MapLiteral *Clone(AstTreeStorage &storage) const override {
|
||||
auto *map = storage.Create<MapLiteral>();
|
||||
for (auto pair : elements_)
|
||||
map->elements_.emplace(pair.first, pair.second->Clone(storage));
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps (property_name, property) to expressions
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, Expression *>
|
||||
elements_;
|
||||
|
||||
protected:
|
||||
MapLiteral(int uid) : BaseLiteral(uid) {}
|
||||
MapLiteral(int uid,
|
||||
const std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *> &elements)
|
||||
: BaseLiteral(uid), elements_(elements) {}
|
||||
};
|
||||
|
||||
class Identifier : public Expression {
|
||||
friend class AstTreeStorage;
|
||||
|
||||
@ -614,23 +646,27 @@ class PropertyLookup : public Expression {
|
||||
|
||||
PropertyLookup *Clone(AstTreeStorage &storage) const override {
|
||||
return storage.Create<PropertyLookup>(expression_->Clone(storage),
|
||||
property_);
|
||||
property_name_, property_);
|
||||
}
|
||||
|
||||
Expression *expression_ = nullptr;
|
||||
GraphDbTypes::Property property_ = nullptr;
|
||||
// TODO potential problem: property lookups are allowed on both map literals
|
||||
// and records, but map literals have strings as keys and records have
|
||||
// GraphDbTypes::Property
|
||||
//
|
||||
// possible solution: store both string and GraphDbTypes::Property here and
|
||||
// choose
|
||||
// between the two depending on Expression result
|
||||
std::string property_name_;
|
||||
GraphDbTypes::Property property_;
|
||||
|
||||
protected:
|
||||
PropertyLookup(int uid, Expression *expression,
|
||||
const std::string &property_name,
|
||||
GraphDbTypes::Property property)
|
||||
: Expression(uid), expression_(expression), property_(property) {}
|
||||
: Expression(uid),
|
||||
expression_(expression),
|
||||
property_name_(property_name),
|
||||
property_(property) {}
|
||||
PropertyLookup(int uid, Expression *expression,
|
||||
std::pair<std::string, GraphDbTypes::Property> property)
|
||||
: Expression(uid),
|
||||
expression_(expression),
|
||||
property_name_(property.first),
|
||||
property_(property.second) {}
|
||||
};
|
||||
|
||||
class LabelsTest : public Expression {
|
||||
@ -838,8 +874,10 @@ class NodeAtom : public PatternAtom {
|
||||
}
|
||||
|
||||
std::vector<GraphDbTypes::Label> labels_;
|
||||
// maps (property_name, property) to an expression
|
||||
// TODO: change to unordered_map
|
||||
std::map<GraphDbTypes::Property, Expression *> properties_;
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, Expression *>
|
||||
properties_;
|
||||
|
||||
protected:
|
||||
using PatternAtom::PatternAtom;
|
||||
@ -887,8 +925,10 @@ class EdgeAtom : public PatternAtom {
|
||||
|
||||
Direction direction_ = Direction::BOTH;
|
||||
std::vector<GraphDbTypes::EdgeType> edge_types_;
|
||||
// maps (property_name, property) to an expression
|
||||
// TODO: change to unordered_map
|
||||
std::map<GraphDbTypes::Property, Expression *> properties_;
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, Expression *>
|
||||
properties_;
|
||||
bool has_range_ = false;
|
||||
Expression *lower_bound_ = nullptr;
|
||||
Expression *upper_bound_ = nullptr;
|
||||
|
@ -24,6 +24,7 @@ class EdgeAtom;
|
||||
class BreadthFirstAtom;
|
||||
class PrimitiveLiteral;
|
||||
class ListLiteral;
|
||||
class MapLiteral;
|
||||
class OrOperator;
|
||||
class XorOperator;
|
||||
class AndOperator;
|
||||
@ -64,10 +65,10 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
|
||||
GreaterEqualOperator, InListOperator, ListIndexingOperator,
|
||||
ListSlicingOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
|
||||
ListLiteral, PropertyLookup, LabelsTest, EdgeTypeTest, Aggregation,
|
||||
Function, All, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
|
||||
BreadthFirstAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
|
||||
RemoveProperty, RemoveLabels, Merge, Unwind>;
|
||||
ListLiteral, MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest,
|
||||
Aggregation, Function, All, Create, Match, Return, With, Pattern, NodeAtom,
|
||||
EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty, SetProperties,
|
||||
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind>;
|
||||
|
||||
using TreeLeafVisitor =
|
||||
::utils::LeafVisitor<Identifier, PrimitiveLiteral, CreateIndex>;
|
||||
@ -89,10 +90,10 @@ using TreeVisitor = ::utils::Visitor<
|
||||
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
|
||||
GreaterEqualOperator, InListOperator, ListIndexingOperator,
|
||||
ListSlicingOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
|
||||
ListLiteral, PropertyLookup, LabelsTest, EdgeTypeTest, Aggregation,
|
||||
Function, All, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
|
||||
BreadthFirstAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
|
||||
RemoveProperty, RemoveLabels, Merge, Unwind, Identifier, PrimitiveLiteral,
|
||||
CreateIndex>;
|
||||
ListLiteral, MapLiteral, PropertyLookup, LabelsTest, EdgeTypeTest,
|
||||
Aggregation, Function, All, Create, Match, Return, With, Pattern, NodeAtom,
|
||||
EdgeAtom, BreadthFirstAtom, Delete, Where, SetProperty, SetProperties,
|
||||
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, Identifier,
|
||||
PrimitiveLiteral, CreateIndex>;
|
||||
|
||||
} // namespace query
|
||||
|
@ -5,8 +5,8 @@
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -177,9 +177,10 @@ antlrcpp::Any CypherMainVisitor::visitCreate(CypherParser::CreateContext *ctx) {
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateIndex(
|
||||
CypherParser::CreateIndexContext *ctx) {
|
||||
std::pair<std::string, GraphDbTypes::Property> key =
|
||||
ctx->propertyKeyName()->accept(this);
|
||||
return storage_.Create<CreateIndex>(
|
||||
ctx_.db_accessor_.label(ctx->labelName()->accept(this)),
|
||||
ctx->propertyKeyName()->accept(this));
|
||||
ctx_.db_accessor_.label(ctx->labelName()->accept(this)), key.second);
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCypherReturn(
|
||||
@ -274,7 +275,8 @@ antlrcpp::Any CypherMainVisitor::visitNodePattern(
|
||||
node->properties_ =
|
||||
ctx->properties()
|
||||
->accept(this)
|
||||
.as<std::map<GraphDbTypes::Property, Expression *>>();
|
||||
.as<std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *>>();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@ -303,9 +305,10 @@ antlrcpp::Any CypherMainVisitor::visitProperties(
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitMapLiteral(
|
||||
CypherParser::MapLiteralContext *ctx) {
|
||||
std::map<GraphDbTypes::Property, Expression *> map;
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, Expression *> map;
|
||||
for (int i = 0; i < static_cast<int>(ctx->propertyKeyName().size()); ++i) {
|
||||
GraphDbTypes::Property key = ctx->propertyKeyName()[i]->accept(this);
|
||||
std::pair<std::string, GraphDbTypes::Property> key =
|
||||
ctx->propertyKeyName()[i]->accept(this);
|
||||
Expression *value = ctx->expression()[i]->accept(this);
|
||||
if (!map.insert({key, value}).second) {
|
||||
throw SemanticException("Same key can't appear twice in map literal");
|
||||
@ -324,7 +327,8 @@ antlrcpp::Any CypherMainVisitor::visitListLiteral(
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPropertyKeyName(
|
||||
CypherParser::PropertyKeyNameContext *ctx) {
|
||||
return ctx_.db_accessor_.property(visitChildren(ctx));
|
||||
const std::string key_name = visitChildren(ctx);
|
||||
return std::make_pair(key_name, ctx_.db_accessor_.property(key_name));
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSymbolicName(
|
||||
@ -450,7 +454,8 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
|
||||
ctx->relationshipDetail()
|
||||
->properties()
|
||||
->accept(this)
|
||||
.as<std::map<GraphDbTypes::Property, Expression *>>();
|
||||
.as<std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *>>();
|
||||
}
|
||||
if (ctx->relationshipDetail()->rangeLiteral()) {
|
||||
edge->has_range_ = true;
|
||||
@ -751,8 +756,9 @@ antlrcpp::Any CypherMainVisitor::visitExpression2b(
|
||||
CypherParser::Expression2bContext *ctx) {
|
||||
Expression *expression = ctx->atom()->accept(this);
|
||||
for (auto *lookup : ctx->propertyLookup()) {
|
||||
std::pair<std::string, GraphDbTypes::Property> key = lookup->accept(this);
|
||||
auto property_lookup =
|
||||
storage_.Create<PropertyLookup>(expression, lookup->accept(this));
|
||||
storage_.Create<PropertyLookup>(expression, key.first, key.second);
|
||||
expression = property_lookup;
|
||||
}
|
||||
return expression;
|
||||
@ -826,8 +832,11 @@ antlrcpp::Any CypherMainVisitor::visitLiteral(
|
||||
return static_cast<BaseLiteral *>(storage_.Create<ListLiteral>(
|
||||
ctx->listLiteral()->accept(this).as<std::vector<Expression *>>()));
|
||||
} else {
|
||||
// TODO: Implement map literal.
|
||||
throw utils::NotYetImplemented("map literal");
|
||||
return static_cast<BaseLiteral *>(storage_.Create<MapLiteral>(
|
||||
ctx->mapLiteral()
|
||||
->accept(this)
|
||||
.as<std::map<std::pair<std::string, GraphDbTypes::Property>,
|
||||
Expression *>>()));
|
||||
}
|
||||
return visitChildren(ctx);
|
||||
}
|
||||
@ -1014,8 +1023,9 @@ antlrcpp::Any CypherMainVisitor::visitPropertyExpression(
|
||||
CypherParser::PropertyExpressionContext *ctx) {
|
||||
Expression *expression = ctx->atom()->accept(this);
|
||||
for (auto *lookup : ctx->propertyLookup()) {
|
||||
std::pair<std::string, GraphDbTypes::Property> key = lookup->accept(this);
|
||||
auto property_lookup =
|
||||
storage_.Create<PropertyLookup>(expression, lookup->accept(this));
|
||||
storage_.Create<PropertyLookup>(expression, key.first, key.second);
|
||||
expression = property_lookup;
|
||||
}
|
||||
// It is guaranteed by grammar that there is at least one propertyLookup.
|
||||
|
@ -210,7 +210,7 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
|
||||
antlrcpp::Any visitProperties(CypherParser::PropertiesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return unordered_map<GraphDbTypes::Property, Expression*>
|
||||
* @return map<std::string, Expression*>
|
||||
*/
|
||||
antlrcpp::Any visitMapLiteral(CypherParser::MapLiteralContext *ctx) override;
|
||||
|
||||
|
@ -249,9 +249,12 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
|
||||
case TypedValue::Type::Edge:
|
||||
return expression_result.Value<EdgeAccessor>().PropsAt(
|
||||
property_lookup.property_);
|
||||
case TypedValue::Type::Map:
|
||||
// TODO implement me
|
||||
throw utils::NotYetImplemented("property lookup on map");
|
||||
case TypedValue::Type::Map: {
|
||||
auto &map = expression_result.Value<std::map<std::string, TypedValue>>();
|
||||
auto found = map.find(property_lookup.property_name_);
|
||||
if (found == map.end()) return TypedValue::Null;
|
||||
return found->second;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException(
|
||||
"Expected Node, Edge or Map for property lookup");
|
||||
@ -311,6 +314,13 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
|
||||
return result;
|
||||
}
|
||||
|
||||
TypedValue Visit(MapLiteral &literal) override {
|
||||
std::map<std::string, TypedValue> result;
|
||||
for (const auto &pair : literal.elements_)
|
||||
result.emplace(pair.first.first, pair.second->Accept(*this));
|
||||
return result;
|
||||
}
|
||||
|
||||
TypedValue Visit(Aggregation &aggregation) override {
|
||||
auto value = frame_[symbol_table_.at(aggregation)];
|
||||
// Aggregation is probably always simple type, but let's switch accessor
|
||||
|
@ -94,7 +94,7 @@ void CreateNode::CreateNodeCursor::Create(Frame &frame,
|
||||
// setting properties on new nodes.
|
||||
ExpressionEvaluator evaluator(frame, symbol_table, db_, GraphView::NEW);
|
||||
for (auto &kv : self_.node_atom_->properties_)
|
||||
PropsSetChecked(new_node, kv.first, kv.second->Accept(evaluator));
|
||||
PropsSetChecked(new_node, kv.first.second, kv.second->Accept(evaluator));
|
||||
frame[symbol_table.at(*self_.node_atom_->identifier_)] = new_node;
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(
|
||||
auto node = db_.insert_vertex();
|
||||
for (auto label : self_.node_atom_->labels_) node.add_label(label);
|
||||
for (auto kv : self_.node_atom_->properties_)
|
||||
PropsSetChecked(node, kv.first, kv.second->Accept(evaluator));
|
||||
PropsSetChecked(node, kv.first.second, kv.second->Accept(evaluator));
|
||||
auto symbol = symbol_table.at(*self_.node_atom_->identifier_);
|
||||
frame[symbol] = node;
|
||||
return frame[symbol].Value<VertexAccessor>();
|
||||
@ -184,7 +184,7 @@ void CreateExpand::CreateExpandCursor::CreateEdge(
|
||||
EdgeAccessor edge =
|
||||
db_.insert_edge(from, to, self_.edge_atom_->edge_types_[0]);
|
||||
for (auto kv : self_.edge_atom_->properties_)
|
||||
PropsSetChecked(edge, kv.first, kv.second->Accept(evaluator));
|
||||
PropsSetChecked(edge, kv.first.second, kv.second->Accept(evaluator));
|
||||
frame[symbol_table.at(*self_.edge_atom_->identifier_)] = edge;
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,21 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(MapLiteral &map_literal) override {
|
||||
debug_assert(
|
||||
map_literal.elements_.size() <= has_aggregation_.size(),
|
||||
"Expected has_aggregation_ flags as much as there are map elements.");
|
||||
bool has_aggr = false;
|
||||
auto it = has_aggregation_.end();
|
||||
std::advance(it, -map_literal.elements_.size());
|
||||
while (it != has_aggregation_.end()) {
|
||||
has_aggr = has_aggr || *it;
|
||||
it = has_aggregation_.erase(it);
|
||||
}
|
||||
has_aggregation_.emplace_back(has_aggr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(All &all) override {
|
||||
// Remove the symbol which is bound by all, because we are only interested
|
||||
// in free (unbound) symbols.
|
||||
@ -1034,7 +1049,7 @@ void Filters::CollectPatternFilters(Pattern &pattern, SymbolTable &symbol_table,
|
||||
symbol_table.CreateSymbol(identifier->name_, false);
|
||||
} else {
|
||||
// Store a PropertyFilter on the value of the property.
|
||||
property_filters_[symbol][prop_pair.first].emplace_back(
|
||||
property_filters_[symbol][prop_pair.first.second].emplace_back(
|
||||
PropertyFilter{collector.symbols_, prop_pair.second});
|
||||
}
|
||||
// Create an equality expression and store it in all_filters_.
|
||||
|
@ -25,25 +25,33 @@ using testing::Pair;
|
||||
using testing::ElementsAre;
|
||||
using testing::UnorderedElementsAre;
|
||||
|
||||
// Base class for all test types
|
||||
class Base {
|
||||
public:
|
||||
Base(const std::string &query) : query_string_(query) {}
|
||||
Dbms dbms_;
|
||||
std::unique_ptr<GraphDbAccessor> db_accessor_ = dbms_.active();
|
||||
Context context_{Config{}, *db_accessor_};
|
||||
std::string query_string_;
|
||||
|
||||
auto Prop(const std::string &prop_name) {
|
||||
return db_accessor_->property(prop_name);
|
||||
}
|
||||
|
||||
auto PropPair(const std::string &prop_name) {
|
||||
return std::make_pair(prop_name, Prop(prop_name));
|
||||
}
|
||||
};
|
||||
|
||||
// This generator uses ast constructed by parsing the query.
|
||||
class AstGenerator {
|
||||
class AstGenerator : public Base {
|
||||
public:
|
||||
AstGenerator(const std::string &query)
|
||||
: dbms_(),
|
||||
db_accessor_(dbms_.active()),
|
||||
context_(Config{}, *db_accessor_),
|
||||
query_string_(query),
|
||||
parser_(query),
|
||||
visitor_(context_),
|
||||
query_([&]() {
|
||||
: Base(query), parser_(query), visitor_(context_), query_([&]() {
|
||||
visitor_.visit(parser_.tree());
|
||||
return visitor_.query();
|
||||
}()) {}
|
||||
|
||||
Dbms dbms_;
|
||||
std::unique_ptr<GraphDbAccessor> db_accessor_;
|
||||
Context context_;
|
||||
std::string query_string_;
|
||||
::frontend::opencypher::Parser parser_;
|
||||
CypherMainVisitor visitor_;
|
||||
Query *query_;
|
||||
@ -63,37 +71,26 @@ class OriginalAfterCloningAstGenerator : public AstGenerator {
|
||||
// This generator clones parsed ast and uses that one.
|
||||
// Original ast is cleared after cloning to ensure that cloned ast doesn't reuse
|
||||
// any data from original ast.
|
||||
class ClonedAstGenerator {
|
||||
class ClonedAstGenerator : public Base {
|
||||
public:
|
||||
ClonedAstGenerator(const std::string &query)
|
||||
: dbms_(),
|
||||
db_accessor_(dbms_.active()),
|
||||
context_(Config{}, *db_accessor_),
|
||||
query_string_(query),
|
||||
query_([&]() {
|
||||
: Base(query), query_([&]() {
|
||||
::frontend::opencypher::Parser parser(query);
|
||||
CypherMainVisitor visitor(context_);
|
||||
visitor.visit(parser.tree());
|
||||
return visitor.query()->Clone(storage);
|
||||
}()) {}
|
||||
|
||||
Dbms dbms_;
|
||||
std::unique_ptr<GraphDbAccessor> db_accessor_;
|
||||
Context context_;
|
||||
std::string query_string_;
|
||||
AstTreeStorage storage;
|
||||
Query *query_;
|
||||
};
|
||||
|
||||
// This generator strips ast, clones it and then plugs stripped out literals in
|
||||
// the same way it is done in ast cacheing in interpreter.
|
||||
class CachedAstGenerator {
|
||||
class CachedAstGenerator : public Base {
|
||||
public:
|
||||
CachedAstGenerator(const std::string &query)
|
||||
: dbms_(),
|
||||
db_accessor_(dbms_.active()),
|
||||
context_(Config{}, *db_accessor_),
|
||||
query_string_(query),
|
||||
: Base(query),
|
||||
storage_([&]() {
|
||||
StrippedQuery stripped(query_string_);
|
||||
::frontend::opencypher::Parser parser(stripped.query());
|
||||
@ -104,10 +101,6 @@ class CachedAstGenerator {
|
||||
}()),
|
||||
query_(storage_.query()) {}
|
||||
|
||||
Dbms dbms_;
|
||||
std::unique_ptr<GraphDbAccessor> db_accessor_;
|
||||
Context context_;
|
||||
std::string query_string_;
|
||||
AstTreeStorage storage_;
|
||||
Query *query_;
|
||||
};
|
||||
@ -742,6 +735,31 @@ TYPED_TEST(CypherMainVisitorTest, ListLiteral) {
|
||||
EXPECT_EQ(elem_2->value_.type(), TypedValue::Type::String);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, MapLiteral) {
|
||||
TypeParam ast_generator("RETURN {a: 1, b: 'bla', c: [1, {a: 42}]}");
|
||||
auto *query = ast_generator.query_;
|
||||
auto *return_clause = dynamic_cast<Return *>(query->clauses_[0]);
|
||||
auto *map_literal = dynamic_cast<MapLiteral *>(
|
||||
return_clause->body_.named_expressions[0]->expression_);
|
||||
ASSERT_TRUE(map_literal);
|
||||
ASSERT_EQ(3, map_literal->elements_.size());
|
||||
auto *elem_0 = dynamic_cast<PrimitiveLiteral *>(
|
||||
map_literal->elements_[ast_generator.PropPair("a")]);
|
||||
ASSERT_TRUE(elem_0);
|
||||
EXPECT_EQ(elem_0->value_.type(), TypedValue::Type::Int);
|
||||
auto *elem_1 = dynamic_cast<PrimitiveLiteral *>(
|
||||
map_literal->elements_[ast_generator.PropPair("b")]);
|
||||
ASSERT_TRUE(elem_1);
|
||||
EXPECT_EQ(elem_1->value_.type(), TypedValue::Type::String);
|
||||
auto *elem_2 = dynamic_cast<ListLiteral *>(
|
||||
map_literal->elements_[ast_generator.PropPair("c")]);
|
||||
ASSERT_TRUE(elem_2);
|
||||
EXPECT_EQ(2, elem_2->elements_.size());
|
||||
auto *elem_2_1 = dynamic_cast<MapLiteral *>(elem_2->elements_[1]);
|
||||
ASSERT_TRUE(elem_2_1);
|
||||
EXPECT_EQ(1, elem_2_1->elements_.size());
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, NodePattern) {
|
||||
TypeParam ast_generator(
|
||||
"MATCH (:label1:label2:label3 {a : 5, b : 10}) RETURN 1");
|
||||
@ -764,7 +782,7 @@ TYPED_TEST(CypherMainVisitorTest, NodePattern) {
|
||||
ast_generator.db_accessor_->label("label1"),
|
||||
ast_generator.db_accessor_->label("label2"),
|
||||
ast_generator.db_accessor_->label("label3")));
|
||||
std::unordered_map<GraphDbTypes::Property, int64_t> properties;
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, int64_t> properties;
|
||||
for (auto x : node->properties_) {
|
||||
auto *literal = dynamic_cast<PrimitiveLiteral *>(x.second);
|
||||
ASSERT_TRUE(literal);
|
||||
@ -772,9 +790,8 @@ TYPED_TEST(CypherMainVisitorTest, NodePattern) {
|
||||
properties[x.first] = literal->value_.Value<int64_t>();
|
||||
}
|
||||
EXPECT_THAT(properties,
|
||||
UnorderedElementsAre(
|
||||
Pair(ast_generator.db_accessor_->property("a"), 5),
|
||||
Pair(ast_generator.db_accessor_->property("b"), 10)));
|
||||
UnorderedElementsAre(Pair(ast_generator.PropPair("a"), 5),
|
||||
Pair(ast_generator.PropPair("b"), 10)));
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, PropertyMapSameKeyAppearsTwice) {
|
||||
@ -858,7 +875,7 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternDetails) {
|
||||
edge->edge_types_,
|
||||
UnorderedElementsAre(ast_generator.db_accessor_->edge_type("type1"),
|
||||
ast_generator.db_accessor_->edge_type("type2")));
|
||||
std::unordered_map<GraphDbTypes::Property, int64_t> properties;
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, int64_t> properties;
|
||||
for (auto x : edge->properties_) {
|
||||
auto *literal = dynamic_cast<PrimitiveLiteral *>(x.second);
|
||||
ASSERT_TRUE(literal);
|
||||
@ -866,9 +883,8 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternDetails) {
|
||||
properties[x.first] = literal->value_.Value<int64_t>();
|
||||
}
|
||||
EXPECT_THAT(properties,
|
||||
UnorderedElementsAre(
|
||||
Pair(ast_generator.db_accessor_->property("a"), 5),
|
||||
Pair(ast_generator.db_accessor_->property("b"), 10)));
|
||||
UnorderedElementsAre(Pair(ast_generator.PropPair("a"), 5),
|
||||
Pair(ast_generator.PropPair("b"), 10)));
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternVariable) {
|
||||
@ -995,8 +1011,8 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUnboundedWithProperty) {
|
||||
EXPECT_TRUE(edge->has_range_);
|
||||
EXPECT_EQ(edge->lower_bound_, nullptr);
|
||||
EXPECT_EQ(edge->upper_bound_, nullptr);
|
||||
auto prop = ast_generator.db_accessor_->property("prop");
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(edge->properties_[prop]);
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(
|
||||
edge->properties_[ast_generator.PropPair("prop")]);
|
||||
EXPECT_EQ(prop_literal->value_.Value<int64_t>(), 42);
|
||||
}
|
||||
|
||||
@ -1011,8 +1027,8 @@ TYPED_TEST(CypherMainVisitorTest,
|
||||
EXPECT_TRUE(edge->has_range_);
|
||||
EXPECT_EQ(edge->lower_bound_, nullptr);
|
||||
EXPECT_EQ(edge->upper_bound_, nullptr);
|
||||
auto prop = ast_generator.db_accessor_->property("prop");
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(edge->properties_[prop]);
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(
|
||||
edge->properties_[ast_generator.PropPair("prop")]);
|
||||
EXPECT_EQ(prop_literal->value_.Value<int64_t>(), 42);
|
||||
ASSERT_EQ(edge->edge_types_.size(), 1U);
|
||||
auto edge_type = ast_generator.db_accessor_->edge_type("edge_type");
|
||||
@ -1031,8 +1047,8 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUpperBoundedWithProperty) {
|
||||
auto upper_bound = dynamic_cast<PrimitiveLiteral *>(edge->upper_bound_);
|
||||
ASSERT_TRUE(upper_bound);
|
||||
EXPECT_EQ(upper_bound->value_.Value<int64_t>(), 2);
|
||||
auto prop = ast_generator.db_accessor_->property("prop");
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(edge->properties_[prop]);
|
||||
auto prop_literal = dynamic_cast<PrimitiveLiteral *>(
|
||||
edge->properties_[ast_generator.PropPair("prop")]);
|
||||
EXPECT_EQ(prop_literal->value_.Value<int64_t>(), 42);
|
||||
}
|
||||
|
||||
@ -1421,5 +1437,4 @@ TYPED_TEST(CypherMainVisitorTest, MatchBfsReturn) {
|
||||
auto *eq = dynamic_cast<EqualOperator *>(bfs->filter_expression_);
|
||||
ASSERT_TRUE(eq);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
/// example:
|
||||
///
|
||||
/// AstTreeStorage storage; // Macros rely on storage being in scope.
|
||||
/// // PROPERTY_LOOKUP and PROPERTY_PAIR macros also rely on a graph DB
|
||||
/// // accessor
|
||||
/// std::unique_ptr<GraphDbAccessor> dba;
|
||||
///
|
||||
/// QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))),
|
||||
/// WHERE(LESS(PROPERTY_LOOKUP("e", edge_prop), LITERAL(3))),
|
||||
@ -23,6 +26,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "database/dbms.hpp"
|
||||
#include "database/graph_db_datatypes.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/interpret/awesome_memgraph_functions.hpp"
|
||||
@ -93,14 +97,32 @@ auto GetOrderBy(T... exprs) {
|
||||
///
|
||||
/// Name is used to create the Identifier which is used for property lookup.
|
||||
///
|
||||
auto GetPropertyLookup(AstTreeStorage &storage, const std::string &name,
|
||||
auto GetPropertyLookup(AstTreeStorage &storage,
|
||||
std::unique_ptr<GraphDbAccessor> &dba,
|
||||
const std::string &name,
|
||||
GraphDbTypes::Property property) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name),
|
||||
dba->property_name(property), property);
|
||||
}
|
||||
auto GetPropertyLookup(AstTreeStorage &storage,
|
||||
std::unique_ptr<GraphDbAccessor> &dba, Expression *expr,
|
||||
GraphDbTypes::Property property) {
|
||||
return storage.Create<PropertyLookup>(expr, dba->property_name(property),
|
||||
property);
|
||||
}
|
||||
auto GetPropertyLookup(AstTreeStorage &storage, Expression *expr,
|
||||
GraphDbTypes::Property property) {
|
||||
return storage.Create<PropertyLookup>(expr, property);
|
||||
auto GetPropertyLookup(
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &dba,
|
||||
const std::string &name,
|
||||
const std::pair<std::string, GraphDbTypes::Property> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name),
|
||||
prop_pair.first, prop_pair.second);
|
||||
}
|
||||
auto GetPropertyLookup(
|
||||
AstTreeStorage &storage, std::unique_ptr<GraphDbAccessor> &dba,
|
||||
Expression *expr,
|
||||
const std::pair<std::string, GraphDbTypes::Property> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(expr, prop_pair.first,
|
||||
prop_pair.second);
|
||||
}
|
||||
|
||||
///
|
||||
@ -422,8 +444,14 @@ auto GetMerge(AstTreeStorage &storage, Pattern *pattern, OnMatch on_match,
|
||||
#define LIST(...) \
|
||||
storage.Create<query::ListLiteral>( \
|
||||
std::vector<query::Expression *>{__VA_ARGS__})
|
||||
#define MAP(...) \
|
||||
storage.Create<query::MapLiteral>( \
|
||||
std::map<std::pair<std::string, GraphDbTypes::Property>, \
|
||||
query::Expression *>{__VA_ARGS__})
|
||||
#define PROPERTY_PAIR(property_name) \
|
||||
std::make_pair(property_name, dba->property(property_name))
|
||||
#define PROPERTY_LOOKUP(...) \
|
||||
query::test_common::GetPropertyLookup(storage, __VA_ARGS__)
|
||||
query::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__)
|
||||
#define NEXPR(name, expr) storage.Create<query::NamedExpression>((name), (expr))
|
||||
// AS is alternative to NEXPR which does not initialize NamedExpression with
|
||||
// Expression. It should be used with RETURN or WITH. For example:
|
||||
@ -473,7 +501,8 @@ auto GetMerge(AstTreeStorage &storage, Pattern *pattern, OnMatch on_match,
|
||||
#define COUNT(expr) \
|
||||
storage.Create<query::Aggregation>((expr), query::Aggregation::Op::COUNT)
|
||||
#define EQ(expr1, expr2) storage.Create<query::EqualOperator>((expr1), (expr2))
|
||||
#define NEQ(expr1, expr2) storage.Create<query::NotEqualOperator>((expr1), (expr2))
|
||||
#define NEQ(expr1, expr2) \
|
||||
storage.Create<query::NotEqualOperator>((expr1), (expr2))
|
||||
#define AND(expr1, expr2) storage.Create<query::AndOperator>((expr1), (expr2))
|
||||
#define OR(expr1, expr2) storage.Create<query::OrOperator>((expr1), (expr2))
|
||||
// Function call
|
||||
|
@ -499,34 +499,54 @@ TEST(ExpressionEvaluator, IsNullOperator) {
|
||||
ASSERT_EQ(val2.Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, PropertyLookup) {
|
||||
class ExpressionEvaluatorPropertyLookup : public testing::Test {
|
||||
protected:
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
|
||||
std::pair<std::string, GraphDbTypes::Property> prop_age =
|
||||
PROPERTY_PAIR("age");
|
||||
std::pair<std::string, GraphDbTypes::Property> prop_height =
|
||||
PROPERTY_PAIR("height");
|
||||
Expression *identifier = storage.Create<Identifier>("element");
|
||||
Symbol symbol = eval.symbol_table.CreateSymbol("element", true);
|
||||
|
||||
void SetUp() { eval.symbol_table[*identifier] = symbol; }
|
||||
|
||||
auto Value(std::pair<std::string, GraphDbTypes::Property> property) {
|
||||
auto *op = storage.Create<PropertyLookup>(identifier, property);
|
||||
return op->Accept(eval.eval);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ExpressionEvaluatorPropertyLookup, Vertex) {
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.PropsSet(dba->property("age"), 10);
|
||||
auto *identifier = storage.Create<Identifier>("n");
|
||||
auto node_symbol = eval.symbol_table.CreateSymbol("n", true);
|
||||
eval.symbol_table[*identifier] = node_symbol;
|
||||
eval.frame[node_symbol] = v1;
|
||||
{
|
||||
auto *op = storage.Create<PropertyLookup>(identifier, dba->property("age"));
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_EQ(value.Value<int64_t>(), 10);
|
||||
}
|
||||
{
|
||||
auto *op =
|
||||
storage.Create<PropertyLookup>(identifier, dba->property("height"));
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_TRUE(value.IsNull());
|
||||
}
|
||||
{
|
||||
eval.frame[node_symbol] = TypedValue::Null;
|
||||
auto *op = storage.Create<PropertyLookup>(identifier, dba->property("age"));
|
||||
auto value = op->Accept(eval.eval);
|
||||
EXPECT_TRUE(value.IsNull());
|
||||
}
|
||||
v1.PropsSet(prop_age.second, 10);
|
||||
eval.frame[symbol] = v1;
|
||||
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
||||
EXPECT_TRUE(Value(prop_height).IsNull());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorPropertyLookup, Edge) {
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto e12 = dba->insert_edge(v1, v2, dba->edge_type("edge_type"));
|
||||
e12.PropsSet(prop_age.second, 10);
|
||||
eval.frame[symbol] = e12;
|
||||
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
||||
EXPECT_TRUE(Value(prop_height).IsNull());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorPropertyLookup, Null) {
|
||||
eval.frame[symbol] = TypedValue::Null;
|
||||
EXPECT_TRUE(Value(prop_age).IsNull());
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorPropertyLookup, MapLiteral) {
|
||||
eval.frame[symbol] = std::map<std::string, TypedValue>{{prop_age.first, 10}};
|
||||
EXPECT_EQ(Value(prop_age).Value<int64_t>(), 10);
|
||||
EXPECT_TRUE(Value(prop_height).IsNull());
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, LabelsTest) {
|
||||
@ -1041,5 +1061,4 @@ TEST(ExpressionEvaluator, FunctionAllWhereWrongType) {
|
||||
eval.symbol_table[*all->identifier_] = x_sym;
|
||||
EXPECT_THROW(all->Accept(eval.eval), QueryRuntimeException);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ TEST(QueryPlan, CreateNodeWithAttributes) {
|
||||
auto dba = dbms.active();
|
||||
|
||||
GraphDbTypes::Label label = dba->label("Person");
|
||||
GraphDbTypes::Property property = dba->label("age");
|
||||
auto property = PROPERTY_PAIR("prop");
|
||||
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
@ -48,7 +48,7 @@ TEST(QueryPlan, CreateNodeWithAttributes) {
|
||||
EXPECT_EQ(vertex.labels().size(), 1);
|
||||
EXPECT_EQ(*vertex.labels().begin(), label);
|
||||
EXPECT_EQ(vertex.Properties().size(), 1);
|
||||
auto prop_eq = vertex.PropsAt(property) == TypedValue(42);
|
||||
auto prop_eq = vertex.PropsAt(property.second) == TypedValue(42);
|
||||
ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
|
||||
EXPECT_TRUE(prop_eq.Value<bool>());
|
||||
}
|
||||
@ -61,7 +61,7 @@ TEST(QueryPlan, CreateReturn) {
|
||||
auto dba = dbms.active();
|
||||
|
||||
GraphDbTypes::Label label = dba->label("Person");
|
||||
GraphDbTypes::Property property = dba->label("age");
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
@ -103,7 +103,7 @@ TEST(QueryPlan, CreateExpand) {
|
||||
|
||||
GraphDbTypes::Label label_node_1 = dba->label("Node1");
|
||||
GraphDbTypes::Label label_node_2 = dba->label("Node2");
|
||||
GraphDbTypes::Property property = dba->label("prop");
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
GraphDbTypes::EdgeType edge_type = dba->label("edge_type");
|
||||
|
||||
SymbolTable symbol_table;
|
||||
@ -143,7 +143,8 @@ TEST(QueryPlan, CreateExpand) {
|
||||
|
||||
EXPECT_EQ(CountIterable(dba->vertices(false)) - before_v,
|
||||
expected_nodes_created);
|
||||
EXPECT_EQ(CountIterable(dba->edges(false)) - before_e, expected_edges_created);
|
||||
EXPECT_EQ(CountIterable(dba->edges(false)) - before_e,
|
||||
expected_edges_created);
|
||||
};
|
||||
|
||||
test_create_path(false, 2, 1);
|
||||
@ -154,10 +155,10 @@ TEST(QueryPlan, CreateExpand) {
|
||||
GraphDbTypes::Label label = vertex.labels()[0];
|
||||
if (label == label_node_1) {
|
||||
// node created by first op
|
||||
EXPECT_EQ(vertex.PropsAt(property).Value<int64_t>(), 1);
|
||||
EXPECT_EQ(vertex.PropsAt(property.second).Value<int64_t>(), 1);
|
||||
} else if (label == label_node_2) {
|
||||
// node create by expansion
|
||||
EXPECT_EQ(vertex.PropsAt(property).Value<int64_t>(), 2);
|
||||
EXPECT_EQ(vertex.PropsAt(property.second).Value<int64_t>(), 2);
|
||||
} else {
|
||||
// should not happen
|
||||
FAIL();
|
||||
@ -165,7 +166,7 @@ TEST(QueryPlan, CreateExpand) {
|
||||
|
||||
for (EdgeAccessor edge : dba->edges(false)) {
|
||||
EXPECT_EQ(edge.edge_type(), edge_type);
|
||||
EXPECT_EQ(edge.PropsAt(property).Value<int64_t>(), 3);
|
||||
EXPECT_EQ(edge.PropsAt(property.second).Value<int64_t>(), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,7 +242,8 @@ TEST(QueryPlan, MatchCreateExpand) {
|
||||
|
||||
EXPECT_EQ(CountIterable(dba->vertices(false)) - before_v,
|
||||
expected_nodes_created);
|
||||
EXPECT_EQ(CountIterable(dba->edges(false)) - before_e, expected_edges_created);
|
||||
EXPECT_EQ(CountIterable(dba->edges(false)) - before_e,
|
||||
expected_edges_created);
|
||||
};
|
||||
|
||||
test_create_path(false, 3, 3);
|
||||
@ -378,10 +380,10 @@ TEST(QueryPlan, DeleteReturn) {
|
||||
auto dba = dbms.active();
|
||||
|
||||
// make a fully-connected (one-direction, no cycles) with 4 nodes
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
auto va = dba->insert_vertex();
|
||||
va.PropsSet(prop, 42);
|
||||
va.PropsSet(prop.second, 42);
|
||||
}
|
||||
|
||||
dba->advance_command();
|
||||
@ -688,8 +690,8 @@ TEST(QueryPlan, NodeFilterSet) {
|
||||
auto dba = dbms.active();
|
||||
// Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto prop = dba->property("prop");
|
||||
v1.PropsSet(prop, 42);
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
v1.PropsSet(prop.second, 42);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto v3 = dba->insert_vertex();
|
||||
auto edge_type = dba->edge_type("Edge");
|
||||
@ -718,7 +720,7 @@ TEST(QueryPlan, NodeFilterSet) {
|
||||
EXPECT_EQ(2, PullAll(set, *dba, symbol_table));
|
||||
dba->advance_command();
|
||||
v1.Reconstruct();
|
||||
auto prop_eq = v1.PropsAt(prop) == TypedValue(42 + 2);
|
||||
auto prop_eq = v1.PropsAt(prop.second) == TypedValue(42 + 2);
|
||||
ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
|
||||
EXPECT_TRUE(prop_eq.Value<bool>());
|
||||
}
|
||||
@ -728,8 +730,8 @@ TEST(QueryPlan, FilterRemove) {
|
||||
auto dba = dbms.active();
|
||||
// Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto prop = dba->property("prop");
|
||||
v1.PropsSet(prop, 42);
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
v1.PropsSet(prop.second, 42);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto v3 = dba->insert_vertex();
|
||||
auto edge_type = dba->edge_type("Edge");
|
||||
@ -756,7 +758,7 @@ TEST(QueryPlan, FilterRemove) {
|
||||
EXPECT_EQ(2, PullAll(rem, *dba, symbol_table));
|
||||
dba->advance_command();
|
||||
v1.Reconstruct();
|
||||
EXPECT_EQ(v1.PropsAt(prop).type(), PropertyValue::Type::Null);
|
||||
EXPECT_EQ(v1.PropsAt(prop.second).type(), PropertyValue::Type::Null);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, SetRemove) {
|
||||
@ -802,7 +804,7 @@ TEST(QueryPlan, Merge) {
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
|
||||
// merge_match branch
|
||||
@ -825,12 +827,12 @@ TEST(QueryPlan, Merge) {
|
||||
v2.Reconstruct();
|
||||
v3.Reconstruct();
|
||||
|
||||
ASSERT_EQ(v1.PropsAt(prop).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v1.PropsAt(prop).Value<int64_t>(), 1);
|
||||
ASSERT_EQ(v2.PropsAt(prop).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v2.PropsAt(prop).Value<int64_t>(), 1);
|
||||
ASSERT_EQ(v3.PropsAt(prop).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v3.PropsAt(prop).Value<int64_t>(), 2);
|
||||
ASSERT_EQ(v1.PropsAt(prop.second).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v1.PropsAt(prop.second).Value<int64_t>(), 1);
|
||||
ASSERT_EQ(v2.PropsAt(prop.second).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v2.PropsAt(prop.second).Value<int64_t>(), 1);
|
||||
ASSERT_EQ(v3.PropsAt(prop.second).type(), PropertyValue::Type::Int);
|
||||
ASSERT_EQ(v3.PropsAt(prop.second).Value<int64_t>(), 2);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, MergeNoInput) {
|
||||
@ -859,7 +861,7 @@ TEST(QueryPlan, SetPropertyOnNull) {
|
||||
auto dba = dbms.active();
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
auto null = LITERAL(TypedValue::Null);
|
||||
auto literal = LITERAL(42);
|
||||
auto n_prop = storage.Create<PropertyLookup>(null, prop);
|
||||
@ -909,7 +911,7 @@ TEST(QueryPlan, RemovePropertyOnNull) {
|
||||
auto dba = dbms.active();
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
auto null = LITERAL(TypedValue::Null);
|
||||
auto n_prop = storage.Create<PropertyLookup>(null, prop);
|
||||
auto once = std::make_shared<Once>();
|
||||
|
@ -118,7 +118,7 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) {
|
||||
|
||||
// add a few nodes to the database
|
||||
GraphDbTypes::Label label = dba->label("Label");
|
||||
GraphDbTypes::Property property = dba->property("Property");
|
||||
auto property = PROPERTY_PAIR("Property");
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto v3 = dba->insert_vertex();
|
||||
@ -132,10 +132,10 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) {
|
||||
v2.add_label(label);
|
||||
v3.add_label(label);
|
||||
// v1 and v4 will have the right properties
|
||||
v1.PropsSet(property, 42);
|
||||
v2.PropsSet(property, 1);
|
||||
v4.PropsSet(property, 42);
|
||||
v5.PropsSet(property, 1);
|
||||
v1.PropsSet(property.second, 42);
|
||||
v2.PropsSet(property.second, 1);
|
||||
v4.PropsSet(property.second, 42);
|
||||
v5.PropsSet(property.second, 1);
|
||||
dba->advance_command();
|
||||
|
||||
AstTreeStorage storage;
|
||||
@ -593,9 +593,12 @@ struct hash<std::pair<int, int>> {
|
||||
class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
protected:
|
||||
Dbms dbms_;
|
||||
std::unique_ptr<GraphDbAccessor> dba_ = dbms_.active();
|
||||
GraphDbTypes::Property prop = dba_->property("property");
|
||||
GraphDbTypes::EdgeType edge_type = dba_->edge_type("edge_type");
|
||||
// style-guide non-conformant name due to PROPERTY_PAIR and PROPERTY_LOOKUP
|
||||
// macro requirements
|
||||
std::unique_ptr<GraphDbAccessor> dba = dbms_.active();
|
||||
std::pair<std::string, GraphDbTypes::Property> prop =
|
||||
PROPERTY_PAIR("property");
|
||||
GraphDbTypes::EdgeType edge_type = dba->edge_type("edge_type");
|
||||
|
||||
// make 4 vertices because we'll need to compare against them exactly
|
||||
// v[0] has `prop` with the value 0
|
||||
@ -614,13 +617,13 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
|
||||
void SetUp() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
v.push_back(dba_->insert_vertex());
|
||||
v.back().PropsSet(prop, i);
|
||||
v.push_back(dba->insert_vertex());
|
||||
v.back().PropsSet(prop.second, i);
|
||||
}
|
||||
|
||||
auto add_edge = [&](int from, int to) {
|
||||
EdgeAccessor edge = dba_->insert_edge(v[from], v[to], edge_type);
|
||||
edge.PropsSet(prop, from * 10 + to);
|
||||
EdgeAccessor edge = dba->insert_edge(v[from], v[to], edge_type);
|
||||
edge.PropsSet(prop.second, from * 10 + to);
|
||||
e.emplace(std::make_pair(from, to), edge);
|
||||
};
|
||||
|
||||
@ -631,7 +634,7 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
add_edge(3, 2);
|
||||
add_edge(2, 2);
|
||||
|
||||
dba_->advance_command();
|
||||
dba->advance_command();
|
||||
for (auto &vertex : v) vertex.Reconstruct();
|
||||
for (auto &edge : e) edge.second.Reconstruct();
|
||||
}
|
||||
@ -664,7 +667,7 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
graph_view);
|
||||
|
||||
Frame frame(symbol_table.max_position());
|
||||
auto cursor = last_op->MakeCursor(*dba_);
|
||||
auto cursor = last_op->MakeCursor(*dba);
|
||||
std::vector<std::pair<std::vector<EdgeAccessor>, VertexAccessor>> results;
|
||||
while (cursor->Pull(frame, symbol_table)) {
|
||||
results.emplace_back(std::vector<EdgeAccessor>(),
|
||||
@ -678,7 +681,7 @@ class QueryPlanExpandBreadthFirst : public testing::Test {
|
||||
|
||||
template <typename TAccessor>
|
||||
auto GetProp(const TAccessor &accessor) {
|
||||
return accessor.PropsAt(prop).template Value<int64_t>();
|
||||
return accessor.PropsAt(prop.second).template Value<int64_t>();
|
||||
}
|
||||
|
||||
Expression *PropNe(Symbol symbol, int value) {
|
||||
@ -757,14 +760,14 @@ TEST_F(QueryPlanExpandBreadthFirst, GraphState) {
|
||||
};
|
||||
EXPECT_EQ(ExpandSize(GraphView::OLD), 3);
|
||||
EXPECT_EQ(ExpandSize(GraphView::NEW), 3);
|
||||
auto new_vertex = dba_->insert_vertex();
|
||||
new_vertex.PropsSet(prop, 4);
|
||||
dba_->insert_edge(v[3], new_vertex, edge_type);
|
||||
EXPECT_EQ(CountIterable(dba_->vertices(false)), 4);
|
||||
EXPECT_EQ(CountIterable(dba_->vertices(true)), 5);
|
||||
auto new_vertex = dba->insert_vertex();
|
||||
new_vertex.PropsSet(prop.second, 4);
|
||||
dba->insert_edge(v[3], new_vertex, edge_type);
|
||||
EXPECT_EQ(CountIterable(dba->vertices(false)), 4);
|
||||
EXPECT_EQ(CountIterable(dba->vertices(true)), 5);
|
||||
EXPECT_EQ(ExpandSize(GraphView::OLD), 3);
|
||||
EXPECT_EQ(ExpandSize(GraphView::NEW), 4);
|
||||
dba_->advance_command();
|
||||
dba->advance_command();
|
||||
EXPECT_EQ(ExpandSize(GraphView::OLD), 4);
|
||||
EXPECT_EQ(ExpandSize(GraphView::NEW), 4);
|
||||
}
|
||||
@ -1129,17 +1132,17 @@ TEST(QueryPlan, EdgeFilter) {
|
||||
edge_types.push_back(dba->edge_type("et" + std::to_string(j)));
|
||||
std::vector<VertexAccessor> vertices;
|
||||
for (int i = 0; i < 7; ++i) vertices.push_back(dba->insert_vertex());
|
||||
GraphDbTypes::Property prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("property");
|
||||
std::vector<EdgeAccessor> edges;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
edges.push_back(
|
||||
dba->insert_edge(vertices[0], vertices[i + 1], edge_types[i % 2]));
|
||||
switch (i % 3) {
|
||||
case 0:
|
||||
edges.back().PropsSet(prop, 42);
|
||||
edges.back().PropsSet(prop.second, 42);
|
||||
break;
|
||||
case 1:
|
||||
edges.back().PropsSet(prop, 100);
|
||||
edges.back().PropsSet(prop.second, 100);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1179,7 +1182,7 @@ TEST(QueryPlan, EdgeFilter) {
|
||||
|
||||
EXPECT_EQ(1, test_filter());
|
||||
// test that edge filtering always filters on old state
|
||||
for (auto &edge : edges) edge.PropsSet(prop, 42);
|
||||
for (auto &edge : edges) edge.PropsSet(prop.second, 42);
|
||||
EXPECT_EQ(1, test_filter());
|
||||
dba->advance_command();
|
||||
EXPECT_EQ(3, test_filter());
|
||||
@ -1230,9 +1233,9 @@ TEST(QueryPlan, Filter) {
|
||||
auto dba = dbms.active();
|
||||
|
||||
// add a 6 nodes with property 'prop', 2 have true as value
|
||||
GraphDbTypes::Property property = dba->property("Property");
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
for (int i = 0; i < 6; ++i)
|
||||
dba->insert_vertex().PropsSet(property, i % 3 == 0);
|
||||
dba->insert_vertex().PropsSet(property.second, i % 3 == 0);
|
||||
dba->insert_vertex(); // prop not set, gives NULL
|
||||
dba->advance_command();
|
||||
|
||||
|
@ -218,10 +218,11 @@ class ExpectOptional : public OpChecker<Optional> {
|
||||
class ExpectScanAllByLabelPropertyValue
|
||||
: public OpChecker<ScanAllByLabelPropertyValue> {
|
||||
public:
|
||||
ExpectScanAllByLabelPropertyValue(GraphDbTypes::Label label,
|
||||
GraphDbTypes::Property property,
|
||||
query::Expression *expression)
|
||||
: label_(label), property_(property), expression_(expression) {}
|
||||
ExpectScanAllByLabelPropertyValue(
|
||||
GraphDbTypes::Label label,
|
||||
const std::pair<std::string, GraphDbTypes::Property> &prop_pair,
|
||||
query::Expression *expression)
|
||||
: label_(label), property_(prop_pair.second), expression_(expression) {}
|
||||
|
||||
void ExpectOp(ScanAllByLabelPropertyValue &scan_all,
|
||||
const SymbolTable &) override {
|
||||
@ -801,13 +802,13 @@ TEST(TestLogicalPlanner, MatchCrossReferenceVariable) {
|
||||
// Test MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
auto m_prop = PROPERTY_LOOKUP("m", prop);
|
||||
auto m_prop = PROPERTY_LOOKUP("m", prop.second);
|
||||
node_n->properties_[prop] = m_prop;
|
||||
auto node_m = NODE("m");
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
|
||||
node_m->properties_[prop] = n_prop;
|
||||
QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN("n"));
|
||||
// We expect both ScanAll to come before filters (2 are joined into one),
|
||||
@ -915,10 +916,9 @@ TEST(TestLogicalPlanner, UnwindMergeNodeProperty) {
|
||||
// Test UNWIND [1] AS i MERGE (n {prop: i})
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
AstTreeStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
node_n->properties_[prop] = IDENT("i");
|
||||
node_n->properties_[PROPERTY_PAIR("prop")] = IDENT("i");
|
||||
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()};
|
||||
@ -965,6 +965,18 @@ TEST(TestLogicalPlanner, ListLiteralAggregationReturn) {
|
||||
CheckPlan(storage, aggr, ExpectProduce());
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, MapLiteralAggregationReturn) {
|
||||
// Test RETURN {sum: SUM(2)} AS result, 42 AS group_by
|
||||
AstTreeStorage storage; Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
QUERY(RETURN(MAP({PROPERTY_PAIR("sum"), sum}), AS("result"), group_by_literal,
|
||||
AS("group_by")));
|
||||
auto aggr = ExpectAggregate({sum}, {group_by_literal});
|
||||
CheckPlan(storage, aggr, ExpectProduce());
|
||||
}
|
||||
|
||||
TEST(TestLogicalPlanner, EmptyListIndexAggregation) {
|
||||
// Test RETURN [][SUM(2)] AS result, 42 AS group_by
|
||||
AstTreeStorage storage;
|
||||
@ -1011,14 +1023,14 @@ TEST(TestLogicalPlanner, AtomIndexedLabelProperty) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto label = dba->label("label");
|
||||
auto property = dba->property("property");
|
||||
auto not_indexed = dba->property("not_indexed");
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
auto not_indexed = PROPERTY_PAIR("not_indexed");
|
||||
auto vertex = dba->insert_vertex();
|
||||
vertex.add_label(label);
|
||||
vertex.PropsSet(property, 42);
|
||||
vertex.PropsSet(property.second, 42);
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label, property);
|
||||
dba->BuildIndex(label, property.second);
|
||||
dba = dbms.active();
|
||||
auto node = NODE("n", label);
|
||||
auto lit_42 = LITERAL(42);
|
||||
@ -1038,9 +1050,9 @@ TEST(TestLogicalPlanner, AtomPropertyWhereLabelIndexing) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto label = dba->label("label");
|
||||
auto property = dba->property("property");
|
||||
auto not_indexed = dba->property("not_indexed");
|
||||
dba->BuildIndex(label, property);
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
auto not_indexed = PROPERTY_PAIR("not_indexed");
|
||||
dba->BuildIndex(label, property.second);
|
||||
dba = dbms.active();
|
||||
auto node = NODE("n");
|
||||
auto lit_42 = LITERAL(42);
|
||||
@ -1063,8 +1075,8 @@ TEST(TestLogicalPlanner, WhereIndexedLabelProperty) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto label = dba->label("label");
|
||||
auto property = dba->property("property");
|
||||
dba->BuildIndex(label, property);
|
||||
auto property = PROPERTY_PAIR("property");
|
||||
dba->BuildIndex(label, property.second);
|
||||
dba = dbms.active();
|
||||
auto lit_42 = LITERAL(42);
|
||||
QUERY(MATCH(PATTERN(NODE("n", label))),
|
||||
@ -1093,10 +1105,10 @@ TEST(TestLogicalPlanner, BestPropertyIndexed) {
|
||||
dba->commit();
|
||||
dba = dbms.active();
|
||||
ASSERT_EQ(dba->vertices_count(label, property), 1);
|
||||
auto better = dba->property("better");
|
||||
dba->BuildIndex(label, better);
|
||||
auto better = PROPERTY_PAIR("better");
|
||||
dba->BuildIndex(label, better.second);
|
||||
dba = dbms.active();
|
||||
ASSERT_EQ(dba->vertices_count(label, better), 0);
|
||||
ASSERT_EQ(dba->vertices_count(label, better.second), 0);
|
||||
auto lit_42 = LITERAL(42);
|
||||
QUERY(MATCH(PATTERN(NODE("n", label))),
|
||||
WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), LITERAL(1)),
|
||||
@ -1116,11 +1128,11 @@ TEST(TestLogicalPlanner, MultiPropertyIndexScan) {
|
||||
auto dba = dbms.active();
|
||||
auto label1 = dba->label("label1");
|
||||
auto label2 = dba->label("label2");
|
||||
auto prop1 = dba->property("prop1");
|
||||
auto prop2 = dba->property("prop2");
|
||||
dba->BuildIndex(label1, prop1);
|
||||
auto prop1 = PROPERTY_PAIR("prop1");
|
||||
auto prop2 = PROPERTY_PAIR("prop2");
|
||||
dba->BuildIndex(label1, prop1.second);
|
||||
dba = dbms.active();
|
||||
dba->BuildIndex(label2, prop2);
|
||||
dba->BuildIndex(label2, prop2.second);
|
||||
dba = dbms.active();
|
||||
AstTreeStorage storage;
|
||||
auto lit_1 = LITERAL(1);
|
||||
@ -1243,7 +1255,7 @@ TEST(TestLogicalPlanner, MatchExpandVariableFiltered) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto type = dba->edge_type("type");
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto edge = EDGE("r", type);
|
||||
edge->has_range_ = true;
|
||||
|
@ -107,8 +107,7 @@ TEST(TestSymbolGenerator, CreatePropertyUnbound) {
|
||||
auto node = NODE("anon");
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
node->properties_[prop] = IDENT("x");
|
||||
node->properties_[PROPERTY_PAIR("prop")] = IDENT("x");
|
||||
auto query_ast = QUERY(CREATE(PATTERN(node)));
|
||||
SymbolGenerator symbol_generator(symbol_table);
|
||||
EXPECT_THROW(query_ast->Accept(symbol_generator), UnboundVariableError);
|
||||
@ -401,10 +400,9 @@ TEST(TestSymbolGenerator, CreateExpandProperty) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto r_type = dba->edge_type("r");
|
||||
auto prop = dba->property("prop");
|
||||
AstTreeStorage storage;
|
||||
auto n_prop = NODE("n");
|
||||
n_prop->properties_[prop] = LITERAL(42);
|
||||
n_prop->properties_[PROPERTY_PAIR("prop")] = LITERAL(42);
|
||||
auto query = QUERY(CREATE(
|
||||
PATTERN(NODE("n"), EDGE("r", r_type, EdgeAtom::Direction::OUT), n_prop)));
|
||||
SymbolTable symbol_table;
|
||||
@ -468,11 +466,11 @@ TEST(TestSymbolGenerator, MatchPropCreateNodeProp) {
|
||||
// Test MATCH (n) CREATE (m {prop: n.prop})
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
auto node_m = NODE("m");
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
|
||||
node_m->properties_[prop] = n_prop;
|
||||
auto query = QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m)));
|
||||
SymbolTable symbol_table;
|
||||
@ -757,13 +755,13 @@ TEST(TestSymbolGenerator, MatchCrossReferenceVariable) {
|
||||
// MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
auto m_prop = PROPERTY_LOOKUP("m", prop);
|
||||
auto m_prop = PROPERTY_LOOKUP("m", prop.second);
|
||||
node_n->properties_[prop] = m_prop;
|
||||
auto node_m = NODE("m");
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
|
||||
node_m->properties_[prop] = n_prop;
|
||||
auto ident_n = IDENT("n");
|
||||
auto as_n = AS("n");
|
||||
@ -852,10 +850,10 @@ TEST(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) {
|
||||
// Test MATCH (n) -[r {prop: n.prop}]- (m) RETURN r
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto edge = EDGE("r");
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
|
||||
edge->properties_[prop] = n_prop;
|
||||
auto node_n = NODE("n");
|
||||
auto query = QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"));
|
||||
@ -971,10 +969,10 @@ TEST(TestSymbolGenerator, MatchPropertySameIdentifier) {
|
||||
// matched symbol is obtained.
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto prop = dba->property("prop");
|
||||
auto prop = PROPERTY_PAIR("prop");
|
||||
AstTreeStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
|
||||
node_n->properties_[prop] = n_prop;
|
||||
auto query = QUERY(MATCH(PATTERN(node_n)), RETURN("n"));
|
||||
SymbolTable symbol_table;
|
||||
|
Loading…
Reference in New Issue
Block a user