Support variable length path in CypherMainVisitor

Summary:
Allow expressions for variable length path bounds

Replace test which expected a syntax exception

Since we now allow variable length to have an arbitrary expression, the
test case is obsolete. It was replaced with something that excepts an
expression which wasn't allowed before.

Reviewers: florijan, mislav.bradac, buda

Reviewed By: mislav.bradac

Subscribers: lion, pullbot

Differential Revision: https://phabricator.memgraph.io/D568
This commit is contained in:
Teon Banek 2017-07-19 15:48:43 +02:00
parent a00ac885ad
commit b4d2d1ff81
6 changed files with 149 additions and 75 deletions

View File

@ -860,6 +860,12 @@ class EdgeAtom : public PatternAtom {
cont = property.second->Accept(visitor);
}
}
if (cont && lower_bound_) {
cont = lower_bound_->Accept(visitor);
}
if (cont && upper_bound_) {
cont = upper_bound_->Accept(visitor);
}
}
return visitor.PostVisit(*this);
}
@ -871,6 +877,9 @@ class EdgeAtom : public PatternAtom {
for (auto property : properties_) {
edge_atom->properties_[property.first] = property.second->Clone(storage);
}
edge_atom->has_range_ = has_range_;
edge_atom->lower_bound_ = lower_bound_ ? lower_bound_->Clone(storage) : nullptr;
edge_atom->upper_bound_ = upper_bound_ ? upper_bound_->Clone(storage) : nullptr;
return edge_atom;
}
@ -878,6 +887,9 @@ class EdgeAtom : public PatternAtom {
std::vector<GraphDbTypes::EdgeType> edge_types_;
// TODO: change to unordered_map
std::map<GraphDbTypes::Property, Expression *> properties_;
bool has_range_ = false;
Expression *lower_bound_ = nullptr;
Expression *upper_bound_ = nullptr;
protected:
using PatternAtom::PatternAtom;

View File

@ -4,6 +4,7 @@
#include <climits>
#include <codecvt>
#include <cstring>
#include <limits>
#include <string>
#include <unordered_map>
#include <utility>
@ -434,17 +435,15 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
.as<std::map<GraphDbTypes::Property, Expression *>>();
}
if (ctx->relationshipDetail()->rangeLiteral()) {
// TODO: implement other clauses.
throw utils::NotYetImplemented("variable relationship length");
edge->has_range_ = true;
auto range = ctx->relationshipDetail()
->rangeLiteral()
->accept(this)
.as<std::pair<Expression *, Expression *>>();
edge->lower_bound_ = range.first;
edge->upper_bound_ = range.second;
}
}
// relationship.has_range = true;
// auto range = ctx->relationshipDetail()
// ->rangeLiteral()
// ->accept(this)
// .as<std::pair<int64_t, int64_t>>();
// relationship.lower_bound = range.first;
// relationship.upper_bound = range.second;
if (!edge->identifier_) {
anonymous_identifiers.push_back(&edge->identifier_);
}
@ -478,29 +477,31 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipTypes(
antlrcpp::Any CypherMainVisitor::visitRangeLiteral(
CypherParser::RangeLiteralContext *ctx) {
if (ctx->integerLiteral().size() == 0U) {
// -[*]-
return std::pair<int64_t, int64_t>(1LL, LLONG_MAX);
} else if (ctx->integerLiteral().size() == 1U) {
debug_assert(ctx->expression().size() <= 2U,
"Expected 0, 1 or 2 bounds in range literal.");
if (ctx->expression().size() == 0U) {
// Case -[*]-
return std::pair<Expression *, Expression *>(nullptr, nullptr);
} else if (ctx->expression().size() == 1U) {
auto dots_tokens = ctx->getTokens(kDotsTokenId);
int64_t bound = ctx->integerLiteral()[0]->accept(this).as<int64_t>();
Expression *bound = ctx->expression()[0]->accept(this);
if (!dots_tokens.size()) {
// -[*2]-
return std::pair<int64_t, int64_t>(bound, bound);
// Case -[*bound]-
return std::pair<Expression *, Expression *>(bound, bound);
}
if (dots_tokens[0]->getSourceInterval().startsAfter(
ctx->integerLiteral()[0]->getSourceInterval())) {
// -[*2..]-
return std::pair<int64_t, int64_t>(bound, LLONG_MAX);
ctx->expression()[0]->getSourceInterval())) {
// Case -[*bound..]-
return std::pair<Expression *, Expression *>(bound, nullptr);
} else {
// -[*..2]-
return std::pair<int64_t, int64_t>(1LL, bound);
// Case -[*..bound]-
return std::pair<Expression *, Expression *>(nullptr, bound);
}
} else {
int64_t lbound = ctx->integerLiteral()[0]->accept(this).as<int64_t>();
int64_t rbound = ctx->integerLiteral()[1]->accept(this).as<int64_t>();
// -[*2..5]-
return std::pair<int64_t, int64_t>(lbound, rbound);
// Case -[*lbound..rbound]-
Expression *lbound = ctx->expression()[0]->accept(this);
Expression *rbound = ctx->expression()[1]->accept(this);
return std::pair<Expression *, Expression *>(lbound, rbound);
}
}

View File

@ -128,7 +128,7 @@ nodeLabels : nodeLabel ( SP? nodeLabel )* ;
nodeLabel : ':' SP? labelName ;
rangeLiteral : '*' SP? ( integerLiteral SP? )? ( '..' SP? ( integerLiteral SP? )? )? ;
rangeLiteral : '*' SP? ( expression SP? )? ( '..' SP? ( expression SP? )? )? ;
labelName : symbolicName ;

View File

@ -307,6 +307,11 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
"Bidirectional relationship are not supported "
"when creating an edge");
}
if (edge_atom.has_range_) {
throw SemanticException(
"Variable length relationships are not supported when creating an "
"edge.");
}
}
scope_.in_property_map = true;
for (auto kv : edge_atom.properties_) {

View File

@ -792,6 +792,10 @@ LogicalOperator *PlanMatching(const Matching &matching,
}
// We have an edge, so generate Expand.
if (expansion.edge) {
if (expansion.edge->has_range_) {
throw utils::NotYetImplemented(
"planning variable length relationships");
}
// If the expand symbols were already bound, then we need to indicate
// that they exist. The Expand will then check whether the pattern holds
// instead of writing the expansion to symbols.

View File

@ -1,5 +1,6 @@
#include <algorithm>
#include <climits>
#include <limits>
#include <string>
#include <unordered_map>
#include <vector>
@ -120,7 +121,7 @@ typedef ::testing::Types<AstGenerator, OriginalAfterCloningAstGenerator,
TYPED_TEST_CASE(CypherMainVisitorTest, AstGeneratorTypes);
TYPED_TEST(CypherMainVisitorTest, SyntaxException) {
ASSERT_THROW(TypeParam("CREATE ()-[*1...2]-()"), SyntaxException);
ASSERT_THROW(TypeParam("CREATE ()-[*1....2]-()"), SyntaxException);
}
TYPED_TEST(CypherMainVisitorTest, SyntaxExceptionOnTrailingText) {
@ -885,54 +886,105 @@ TYPED_TEST(CypherMainVisitorTest, RelationshipPatternVariable) {
EXPECT_TRUE(edge->identifier_->user_declared_);
}
// // Relationship with unbounded variable range.
// TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUnbounded) {
// ParserTables parser("CREATE ()-[*]-()");
// ASSERT_EQ(parser.identifiers_map_.size(), 0U);
// ASSERT_EQ(parser.relationships_.size(), 1U);
// CompareRelationships(*parser.relationships_.begin(),
// Relationship::Direction::BOTH, {}, {}, true, 1,
// LLONG_MAX);
// }
//
// // Relationship with lower bounded variable range.
// TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerBounded) {
// ParserTables parser("CREATE ()-[*5..]-()");
// ASSERT_EQ(parser.identifiers_map_.size(), 0U);
// ASSERT_EQ(parser.relationships_.size(), 1U);
// CompareRelationships(*parser.relationships_.begin(),
// Relationship::Direction::BOTH, {}, {}, true, 5,
// LLONG_MAX);
// }
//
// // Relationship with upper bounded variable range.
// TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUpperBounded) {
// ParserTables parser("CREATE ()-[*..10]-()");
// ASSERT_EQ(parser.identifiers_map_.size(), 0U);
// ASSERT_EQ(parser.relationships_.size(), 1U);
// CompareRelationships(*parser.relationships_.begin(),
// Relationship::Direction::BOTH, {}, {}, true, 1, 10);
// }
//
// // Relationship with lower and upper bounded variable range.
// TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerUpperBounded) {
// ParserTables parser("CREATE ()-[*5..10]-()");
// ASSERT_EQ(parser.identifiers_map_.size(), 0U);
// ASSERT_EQ(parser.relationships_.size(), 1U);
// CompareRelationships(*parser.relationships_.begin(),
// Relationship::Direction::BOTH, {}, {}, true, 5, 10);
// }
//
// // Relationship with fixed number of edges.
// TYPED_TEST(CypherMainVisitorTest, RelationshipPatternFixedRange) {
// ParserTables parser("CREATE ()-[*10]-()");
// ASSERT_EQ(parser.identifiers_map_.size(), 0U);
// ASSERT_EQ(parser.relationships_.size(), 1U);
// CompareRelationships(*parser.relationships_.begin(),
// Relationship::Direction::BOTH, {}, {}, true, 10, 10);
// }
//
//
// Assert that match has a single pattern with a single edge atom and store it
// in edge parameter.
void AssertMatchSingleEdgeAtom(Match *match, EdgeAtom *&edge) {
ASSERT_TRUE(match);
ASSERT_EQ(match->patterns_.size(), 1U);
ASSERT_EQ(match->patterns_[0]->atoms_.size(), 3U);
edge = dynamic_cast<EdgeAtom *>(match->patterns_[0]->atoms_[1]);
ASSERT_TRUE(edge);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUnbounded) {
TypeParam ast_generator("MATCH ()-[r*]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->lower_bound_, nullptr);
EXPECT_EQ(edge->upper_bound_, nullptr);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerBounded) {
TypeParam ast_generator("MATCH ()-[r*42..]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
auto *lower_bound = dynamic_cast<PrimitiveLiteral *>(edge->lower_bound_);
ASSERT_TRUE(lower_bound);
EXPECT_TRUE(lower_bound->value_.Value<int64_t>() == 42);
EXPECT_EQ(edge->upper_bound_, nullptr);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternUpperBounded) {
TypeParam ast_generator("MATCH ()-[r*..42]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
EXPECT_EQ(edge->lower_bound_, nullptr);
auto upper_bound = dynamic_cast<PrimitiveLiteral *>(edge->upper_bound_);
ASSERT_TRUE(upper_bound);
EXPECT_EQ(upper_bound->value_.Value<int64_t>(), 42);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternLowerUpperBounded) {
TypeParam ast_generator("MATCH ()-[r*24..42]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
auto lower_bound = dynamic_cast<PrimitiveLiteral *>(edge->lower_bound_);
ASSERT_TRUE(lower_bound);
EXPECT_EQ(lower_bound->value_.Value<int64_t>(), 24);
auto upper_bound = dynamic_cast<PrimitiveLiteral *>(edge->upper_bound_);
ASSERT_TRUE(upper_bound);
EXPECT_EQ(upper_bound->value_.Value<int64_t>(), 42);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternFixedRange) {
TypeParam ast_generator("MATCH ()-[r*42]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
auto lower_bound = dynamic_cast<PrimitiveLiteral *>(edge->lower_bound_);
ASSERT_TRUE(lower_bound);
EXPECT_EQ(lower_bound->value_.Value<int64_t>(), 42);
auto upper_bound = dynamic_cast<PrimitiveLiteral *>(edge->upper_bound_);
ASSERT_TRUE(upper_bound);
EXPECT_EQ(upper_bound->value_.Value<int64_t>(), 42);
}
TYPED_TEST(CypherMainVisitorTest, RelationshipPatternFloatingUpperBound) {
// [r*1...2] should be parsed as [r*1..0.2]
TypeParam ast_generator("MATCH ()-[r*1...2]->() RETURN r");
auto *query = ast_generator.query_;
auto *match = dynamic_cast<Match *>(query->clauses_[0]);
EdgeAtom *edge = nullptr;
AssertMatchSingleEdgeAtom(match, edge);
EXPECT_EQ(edge->direction_, EdgeAtom::Direction::OUT);
EXPECT_TRUE(edge->has_range_);
auto lower_bound = dynamic_cast<PrimitiveLiteral *>(edge->lower_bound_);
ASSERT_TRUE(lower_bound);
EXPECT_EQ(lower_bound->value_.Value<int64_t>(), 1);
auto upper_bound = dynamic_cast<PrimitiveLiteral *>(edge->upper_bound_);
ASSERT_TRUE(upper_bound);
EXPECT_EQ(upper_bound->value_.Value<double>(), 0.2);
}
// // PatternPart with variable.
// TYPED_TEST(CypherMainVisitorTest, PatternPartVariable) {
// ParserTables parser("CREATE var=()--()");