Add grammar for unique constraints

Summary:
This should be the same as Neo4J grammar. Please check:
https://neo4j.com/docs/cypher-refcard/current/

Reviewers: teon.banek, msantl, llugovic

Reviewed By: msantl

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1936
This commit is contained in:
Marin Tomic 2019-03-28 12:49:23 +01:00
parent 750115e8ff
commit 3965d2341b
7 changed files with 159 additions and 82 deletions

View File

@ -2560,8 +2560,8 @@ cpp<#
(:serialize (:slk) (:capnp))
(:clone))
(lcp:define-class constraint-query (query)
((action-type "ActionType" :scope :public)
(lcp:define-struct constraint ()
((type "Type")
(label "LabelIx" :scope :public
:slk-load (lambda (member)
#>cpp
@ -2589,6 +2589,20 @@ cpp<#
return ix;
}")
:clone (clone-name-ix-vector "Property")))
(:public
(lcp:define-enum type (exists unique)
(:serialize (:lcp) (:capnp))))
(:serialize (:slk :load-args '((storage "query::AstStorage *")))
(:capnp :load-args '((storage "AstStorage *"))))
(:clone :args '((storage "AstStorage *"))))
(lcp:define-class constraint-query (query)
((action-type "ActionType" :scope :public)
(constraint "Constraint" :scope :public
:slk-load (lambda (member)
#>cpp
slk::Load(&self->${member}, reader, storage);
cpp<#)))
(:public
(lcp:define-enum action-type
(create drop)

View File

@ -68,52 +68,44 @@ antlrcpp::Any CypherMainVisitor::visitInfoQuery(
antlrcpp::Any CypherMainVisitor::visitConstraintQuery(
MemgraphCypher::ConstraintQueryContext *ctx) {
CHECK(ctx->children.size() == 1)
<< "ConstraintQuery should have exactly one child!";
query_ = ctx->children[0]->accept(this).as<ConstraintQuery *>();
auto *constraint_query = storage_->Create<ConstraintQuery>();
CHECK(ctx->CREATE() || ctx->DROP());
if (ctx->CREATE()) {
constraint_query->action_type_ = ConstraintQuery::ActionType::CREATE;
} else if (ctx->DROP()) {
constraint_query->action_type_ = ConstraintQuery::ActionType::DROP;
}
constraint_query->constraint_ =
ctx->constraint()->accept(this).as<Constraint>();
query_ = constraint_query;
return query_;
}
antlrcpp::Any CypherMainVisitor::visitCreateConstraint(
MemgraphCypher::CreateConstraintContext *ctx) {
auto *constraint_query = storage_->Create<ConstraintQuery>();
constraint_query->action_type_ = ConstraintQuery::ActionType::CREATE;
antlrcpp::Any CypherMainVisitor::visitConstraint(
MemgraphCypher::ConstraintContext *ctx) {
Constraint constraint;
CHECK(ctx->EXISTS() || ctx->UNIQUE());
if (ctx->EXISTS()) {
constraint.type = Constraint::Type::EXISTS;
} else if (ctx->UNIQUE()) {
constraint.type = Constraint::Type::UNIQUE;
}
constraint.label = AddLabel(ctx->labelName()->accept(this));
std::string node_name = ctx->nodeName->symbolicName()->accept(this);
for (const auto &var_ctx : ctx->variable()) {
for (const auto &var_ctx : ctx->constraintPropertyList()->variable()) {
std::string var_name = var_ctx->symbolicName()->accept(this);
if (var_name != node_name) {
throw SemanticException("All variables should reference node '{}'.",
node_name);
throw SemanticException(
"All constraint variable should reference node '{}'", node_name);
}
}
constraint_query->label_ = AddLabel(ctx->labelName()->accept(this));
constraint_query->properties_.reserve(ctx->propertyLookup().size());
for (const auto &prop_lookup : ctx->propertyLookup()) {
PropertyIx name_key = prop_lookup->propertyKeyName()->accept(this);
constraint_query->properties_.push_back(name_key);
for (const auto &prop_lookup :
ctx->constraintPropertyList()->propertyLookup()) {
constraint.properties.push_back(
prop_lookup->propertyKeyName()->accept(this));
}
return constraint_query;
}
antlrcpp::Any CypherMainVisitor::visitDropConstraint(
MemgraphCypher::DropConstraintContext *ctx) {
auto *constraint_query = storage_->Create<ConstraintQuery>();
constraint_query->action_type_ = ConstraintQuery::ActionType::DROP;
std::string node_name = ctx->nodeName->symbolicName()->accept(this);
for (const auto &var_ctx : ctx->variable()) {
std::string var_name = var_ctx->symbolicName()->accept(this);
if (var_name != node_name) {
throw SemanticException("All variables should reference node '{}'.",
node_name);
}
}
constraint_query->label_ = AddLabel(ctx->labelName()->accept(this));
constraint_query->properties_.reserve(ctx->propertyLookup().size());
for (const auto &prop_lookup : ctx->propertyLookup()) {
PropertyIx name_key = prop_lookup->propertyKeyName()->accept(this);
constraint_query->properties_.push_back(name_key);
}
return constraint_query;
return constraint;
}
antlrcpp::Any CypherMainVisitor::visitCypherQuery(
@ -1364,8 +1356,7 @@ antlrcpp::Any CypherMainVisitor::visitExpression2a(
MemgraphCypher::Expression2aContext *ctx) {
Expression *expression = ctx->expression2b()->accept(this);
if (ctx->nodeLabels()) {
auto labels =
ctx->nodeLabels()->accept(this).as<std::vector<LabelIx>>();
auto labels = ctx->nodeLabels()->accept(this).as<std::vector<LabelIx>>();
expression = storage_->Create<LabelsTest>(expression, labels);
}
return expression;

View File

@ -164,24 +164,18 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitInfoQuery(MemgraphCypher::InfoQueryContext *ctx) override;
/**
* @return Constraint
*/
antlrcpp::Any visitConstraint(
MemgraphCypher::ConstraintContext *ctx) override;
/**
* @return ConstraintQuery*
*/
antlrcpp::Any visitConstraintQuery(
MemgraphCypher::ConstraintQueryContext *ctx) override;
/**
* @return ConstraintQuery*
*/
antlrcpp::Any visitCreateConstraint(
MemgraphCypher::CreateConstraintContext *ctx) override;
/**
* @return ConstraintQuery*
*/
antlrcpp::Any visitDropConstraint(
MemgraphCypher::DropConstraintContext *ctx) override;
/**
* @return AuthQuery*
*/

View File

@ -31,13 +31,13 @@ query : cypherQuery
| constraintQuery
;
constraintQuery : createConstraint | dropConstraint ;
constraintQuery : ( CREATE | DROP ) constraint ;
createConstraint : CREATE CONSTRAINT ON '(' nodeName=variable ':' labelName ')'
ASSERT EXISTS '(' variable propertyLookup ( ',' variable propertyLookup )* ')' ;
constraint : CONSTRAINT ON '(' nodeName=variable ':' labelName ')' ASSERT EXISTS '(' constraintPropertyList ')'
| CONSTRAINT ON '(' nodeName=variable ':' labelName ')' ASSERT constraintPropertyList IS UNIQUE
;
dropConstraint : DROP CONSTRAINT ON '(' nodeName=variable ':' labelName ')'
ASSERT EXISTS '(' variable propertyLookup ( ',' variable propertyLookup )* ')' ;
constraintPropertyList : variable propertyLookup ( ',' variable propertyLookup )* ;
storageInfo : STORAGE INFO ;

View File

@ -583,7 +583,8 @@ Callback HandleIndexQuery(IndexQuery *index_query,
}
}
Callback HandleInfoQuery(InfoQuery *info_query, database::GraphDbAccessor *db_accessor) {
Callback HandleInfoQuery(InfoQuery *info_query,
database::GraphDbAccessor *db_accessor) {
Callback callback;
switch (info_query->info_type_) {
case InfoQuery::InfoType::STORAGE:
@ -638,21 +639,28 @@ Callback HandleConstraintQuery(ConstraintQuery *constraint_query,
database::GraphDbAccessor *db_accessor) {
Callback callback;
std::vector<std::string> property_names;
property_names.reserve(constraint_query->properties_.size());
for (const auto &prop_ix : constraint_query->properties_) {
property_names.reserve(constraint_query->constraint_.properties.size());
for (const auto &prop_ix : constraint_query->constraint_.properties) {
property_names.push_back(prop_ix.name);
}
std::string label_name = constraint_query->label_.name;
std::string label_name = constraint_query->constraint_.label.name;
std::string type;
switch (constraint_query->constraint_.type) {
case Constraint::Type::EXISTS:
type = "exists";
break;
case Constraint::Type::UNIQUE:
type = "unique";
break;
}
switch (constraint_query->action_type_) {
case ConstraintQuery::ActionType::CREATE:
throw utils::NotYetImplemented("create constraint :{}({}) exists",
label_name,
utils::Join(property_names, ", "));
throw utils::NotYetImplemented("create constraint :{}({}) {}", label_name,
utils::Join(property_names, ", "), type);
break;
case ConstraintQuery::ActionType::DROP:
throw utils::NotYetImplemented("drop constraint :{}({}) exists",
label_name,
utils::Join(property_names, ", "));
throw utils::NotYetImplemented("drop constraint :{}({}) {}", label_name,
utils::Join(property_names, ", "), type);
break;
}
return callback;

View File

@ -2697,6 +2697,27 @@ TEST_P(CypherMainVisitorTest, CreateConstraintSyntaxError) {
EXPECT_THROW(ast_generator.ParseQuery("CREATE CONSTRAINT ON (n:label) ASSERT "
"EXISTS (n.prop1, missing.prop2)"),
SemanticException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE CONSTRAINT ON (n:label) ASSERT "
"EXISTS (m.prop1, m.prop2)"),
SemanticException);
EXPECT_THROW(ast_generator.ParseQuery(
"CREATE CONSTRAINT ON (:label) ASSERT IS UNIQUE"),
SyntaxException);
EXPECT_THROW(
ast_generator.ParseQuery("CREATE CONSTRAINT () ASSERT IS UNIQUE"),
SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery(
"CREATE CONSTRAINT ON () ASSERT prop1 IS UNIQUE"),
SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery(
"CREATE CONSTRAINT ON () ASSERT prop1, prop2 IS UNIQUE"),
SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE CONSTRAINT ON (n:label) ASSERT "
"n.prop1, missing.prop2 IS UNIQUE"),
SemanticException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE CONSTRAINT ON (n:label) ASSERT "
"m.prop1, m.prop2 IS UNIQUE"),
SemanticException);
}
TEST_P(CypherMainVisitorTest, CreateConstraint) {
@ -2706,8 +2727,9 @@ TEST_P(CypherMainVisitorTest, CreateConstraint) {
"CREATE CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
EXPECT_THAT(query->properties_,
EXPECT_EQ(query->constraint_.type, Constraint::Type::EXISTS);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1")));
}
{
@ -2716,8 +2738,32 @@ TEST_P(CypherMainVisitorTest, CreateConstraint) {
"CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.prop1, n.prop2)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
EXPECT_THAT(query->properties_,
EXPECT_EQ(query->constraint_.type, Constraint::Type::EXISTS);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1"),
ast_generator.Prop("prop2")));
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.ParseQuery(
"CREATE CONSTRAINT ON (n:label) ASSERT n.prop1 IS UNIQUE"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
EXPECT_EQ(query->constraint_.type, Constraint::Type::UNIQUE);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1")));
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.ParseQuery(
"CREATE CONSTRAINT ON (n:label) ASSERT n.prop1, n.prop2 IS UNIQUE"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
EXPECT_EQ(query->constraint_.type, Constraint::Type::UNIQUE);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1"),
ast_generator.Prop("prop2")));
}
@ -2730,8 +2776,9 @@ TEST_P(CypherMainVisitorTest, DropConstraint) {
"DROP CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
EXPECT_THAT(query->properties_,
EXPECT_EQ(query->constraint_.type, Constraint::Type::EXISTS);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1")));
}
{
@ -2740,8 +2787,32 @@ TEST_P(CypherMainVisitorTest, DropConstraint) {
"DROP CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1, n.prop2)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
EXPECT_THAT(query->properties_,
EXPECT_EQ(query->constraint_.type, Constraint::Type::EXISTS);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1"),
ast_generator.Prop("prop2")));
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.ParseQuery(
"DROP CONSTRAINT ON (n:label) ASSERT n.prop1 IS UNIQUE"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
EXPECT_EQ(query->constraint_.type, Constraint::Type::UNIQUE);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1")));
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.ParseQuery(
"DROP CONSTRAINT ON (n:label) ASSERT n.prop1, n.prop2 IS UNIQUE"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
EXPECT_EQ(query->constraint_.type, Constraint::Type::UNIQUE);
EXPECT_EQ(query->constraint_.label, ast_generator.Label("label"));
EXPECT_THAT(query->constraint_.properties,
UnorderedElementsAre(ast_generator.Prop("prop1"),
ast_generator.Prop("prop2")));
}

View File

@ -157,9 +157,9 @@ TEST_F(TestPrivilegeExtractor, ShowConstraintInfo) {
TEST_F(TestPrivilegeExtractor, CreateConstraint) {
auto *query = storage.Create<ConstraintQuery>();
query->action_type_ = ConstraintQuery::ActionType::CREATE;
query->label_ = storage.GetLabelIx("label");
query->properties_.push_back(storage.GetPropertyIx("prop0"));
query->properties_.push_back(storage.GetPropertyIx("prop1"));
query->constraint_.label = storage.GetLabelIx("label");
query->constraint_.properties.push_back(storage.GetPropertyIx("prop0"));
query->constraint_.properties.push_back(storage.GetPropertyIx("prop1"));
EXPECT_THAT(GetRequiredPrivileges(query),
UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
}
@ -167,10 +167,9 @@ TEST_F(TestPrivilegeExtractor, CreateConstraint) {
TEST_F(TestPrivilegeExtractor, DropConstraint) {
auto *query = storage.Create<ConstraintQuery>();
query->action_type_ = ConstraintQuery::ActionType::DROP;
query->label_ = storage.GetLabelIx("label");
query->properties_.push_back(storage.GetPropertyIx("prop0"));
query->properties_.push_back(storage.GetPropertyIx("prop1"));
query->constraint_.label = storage.GetLabelIx("label");
query->constraint_.properties.push_back(storage.GetPropertyIx("prop0"));
query->constraint_.properties.push_back(storage.GetPropertyIx("prop1"));
EXPECT_THAT(GetRequiredPrivileges(query),
UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
}