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:
parent
750115e8ff
commit
3965d2341b
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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*
|
||||
*/
|
||||
|
@ -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 ;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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")));
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user