Add syntax for managing data constraints
Reviewers: mtomic, mferencevic, buda, msantl Reviewed By: mtomic, msantl Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1879
This commit is contained in:
parent
7be23896c2
commit
4d1d9fb15a
@ -40,6 +40,8 @@ std::string PermissionToString(Permission permission) {
|
||||
return "AUTH";
|
||||
case Permission::STREAM:
|
||||
return "STREAM";
|
||||
case Permission::CONSTRAINT:
|
||||
return "CONSTRAINT";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ enum class Permission : uint64_t {
|
||||
REMOVE = 0x00000020,
|
||||
INDEX = 0x00000040,
|
||||
STATS = 0x00000080,
|
||||
CONSTRAINT = 0x00000100,
|
||||
AUTH = 0x00010000,
|
||||
STREAM = 0x00020000,
|
||||
};
|
||||
|
@ -24,6 +24,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
||||
return auth::Permission::AUTH;
|
||||
case query::AuthQuery::Privilege::STREAM:
|
||||
return auth::Permission::STREAM;
|
||||
case query::AuthQuery::Privilege::CONSTRAINT:
|
||||
return auth::Permission::CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2402,7 +2402,7 @@ cpp<#
|
||||
show-users-for-role)
|
||||
(:serialize))
|
||||
(lcp:define-enum privilege
|
||||
(create delete match merge set remove index stats auth stream)
|
||||
(create delete match merge set remove index stats auth stream constraint)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
AuthQuery() = default;
|
||||
@ -2436,7 +2436,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::STATS,
|
||||
AuthQuery::Privilege::AUTH, AuthQuery::Privilege::STREAM};
|
||||
AuthQuery::Privilege::AUTH, AuthQuery::Privilege::STREAM,
|
||||
AuthQuery::Privilege::CONSTRAINT};
|
||||
cpp<#
|
||||
|
||||
(lcp:define-class stream-query (query)
|
||||
@ -2515,7 +2516,47 @@ cpp<#
|
||||
((info-type "InfoType" :scope :public))
|
||||
(:public
|
||||
(lcp:define-enum info-type
|
||||
(storage index)
|
||||
(storage index constraint)
|
||||
(:serialize))
|
||||
|
||||
#>cpp
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:serialize (:slk) (:capnp))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class constraint-query (query)
|
||||
((action-type "ActionType" :scope :public)
|
||||
(label "LabelIx" :scope :public
|
||||
:slk-load (lambda (member)
|
||||
#>cpp
|
||||
slk::Load(&self->${member}, reader, storage);
|
||||
cpp<#)
|
||||
:clone (lambda (source dest)
|
||||
#>cpp
|
||||
${dest} = storage->GetLabelIx(${source}.name);
|
||||
cpp<#))
|
||||
(properties "std::vector<PropertyIx>" :scope :public
|
||||
:slk-load (lambda (member)
|
||||
#>cpp
|
||||
size_t size = 0;
|
||||
slk::Load(&size, reader);
|
||||
self->${member}.resize(size);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
slk::Load(&self->${member}[i], reader, storage);
|
||||
}
|
||||
cpp<#)
|
||||
:capnp-load (lcp:capnp-load-vector
|
||||
"capnp::PropertyIx" "PropertyIx"
|
||||
"[storage](const auto &reader) {
|
||||
PropertyIx ix;
|
||||
Load(&ix, reader, storage);
|
||||
return ix;
|
||||
}")
|
||||
:clone (clone-name-ix-vector "Property")))
|
||||
(:public
|
||||
(lcp:define-enum action-type
|
||||
(create drop)
|
||||
(:serialize))
|
||||
|
||||
#>cpp
|
||||
|
@ -67,6 +67,7 @@ class ProfileQuery;
|
||||
class IndexQuery;
|
||||
class StreamQuery;
|
||||
class InfoQuery;
|
||||
class ConstraintQuery;
|
||||
|
||||
using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator,
|
||||
@ -110,6 +111,7 @@ class ExpressionVisitor
|
||||
template <class TResult>
|
||||
class QueryVisitor
|
||||
: public ::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery,
|
||||
IndexQuery, AuthQuery, StreamQuery, InfoQuery> {};
|
||||
IndexQuery, AuthQuery, StreamQuery, InfoQuery,
|
||||
ConstraintQuery> {};
|
||||
|
||||
} // namespace query
|
||||
|
@ -49,7 +49,7 @@ antlrcpp::Any CypherMainVisitor::visitProfileQuery(
|
||||
antlrcpp::Any CypherMainVisitor::visitInfoQuery(
|
||||
MemgraphCypher::InfoQueryContext *ctx) {
|
||||
CHECK(ctx->children.size() == 2)
|
||||
<< "ProfileQuery should have exactly two children!";
|
||||
<< "InfoQuery should have exactly two children!";
|
||||
auto *info_query = storage_->Create<InfoQuery>();
|
||||
query_ = info_query;
|
||||
if (ctx->storageInfo()) {
|
||||
@ -58,11 +58,64 @@ antlrcpp::Any CypherMainVisitor::visitInfoQuery(
|
||||
} else if (ctx->indexInfo()) {
|
||||
info_query->info_type_ = InfoQuery::InfoType::INDEX;
|
||||
return info_query;
|
||||
} else if (ctx->constraintInfo()) {
|
||||
info_query->info_type_ = InfoQuery::InfoType::CONSTRAINT;
|
||||
return info_query;
|
||||
} else {
|
||||
throw utils::NotYetImplemented("Info query: '{}'", ctx->getText());
|
||||
}
|
||||
}
|
||||
|
||||
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 *>();
|
||||
return query_;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateConstraint(
|
||||
MemgraphCypher::CreateConstraintContext *ctx) {
|
||||
auto *constraint_query = storage_->Create<ConstraintQuery>();
|
||||
constraint_query->action_type_ = ConstraintQuery::ActionType::CREATE;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCypherQuery(
|
||||
MemgraphCypher::CypherQueryContext *ctx) {
|
||||
auto *cypher_query = storage_->Create<CypherQuery>();
|
||||
@ -495,6 +548,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(
|
||||
if (ctx->STATS()) return AuthQuery::Privilege::STATS;
|
||||
if (ctx->AUTH()) return AuthQuery::Privilege::AUTH;
|
||||
if (ctx->STREAM()) return AuthQuery::Privilege::STREAM;
|
||||
if (ctx->CONSTRAINT()) return AuthQuery::Privilege::CONSTRAINT;
|
||||
LOG(FATAL) << "Should not get here - unknown privilege!";
|
||||
}
|
||||
|
||||
|
@ -164,6 +164,24 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitInfoQuery(MemgraphCypher::InfoQueryContext *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*
|
||||
*/
|
||||
|
@ -28,13 +28,24 @@ query : cypherQuery
|
||||
| explainQuery
|
||||
| profileQuery
|
||||
| infoQuery
|
||||
| constraintQuery
|
||||
;
|
||||
|
||||
constraintQuery : createConstraint | dropConstraint ;
|
||||
|
||||
createConstraint : CREATE CONSTRAINT ON '(' nodeName=variable ':' labelName ')'
|
||||
ASSERT EXISTS '(' variable propertyLookup ( ',' variable propertyLookup )* ')' ;
|
||||
|
||||
dropConstraint : DROP CONSTRAINT ON '(' nodeName=variable ':' labelName ')'
|
||||
ASSERT EXISTS '(' variable propertyLookup ( ',' variable propertyLookup )* ')' ;
|
||||
|
||||
storageInfo : STORAGE INFO ;
|
||||
|
||||
indexInfo : INDEX INFO ;
|
||||
|
||||
infoQuery : SHOW ( storageInfo | indexInfo ) ;
|
||||
constraintInfo : CONSTRAINT INFO ;
|
||||
|
||||
infoQuery : SHOW ( storageInfo | indexInfo | constraintInfo ) ;
|
||||
|
||||
explainQuery : EXPLAIN cypherQuery ;
|
||||
|
||||
@ -297,9 +308,11 @@ cypherKeyword : ALL
|
||||
| AS
|
||||
| ASC
|
||||
| ASCENDING
|
||||
| ASSERT
|
||||
| BFS
|
||||
| BY
|
||||
| CASE
|
||||
| CONSTRAINT
|
||||
| CONTAINS
|
||||
| COUNT
|
||||
| CREATE
|
||||
@ -312,12 +325,14 @@ cypherKeyword : ALL
|
||||
| ELSE
|
||||
| END
|
||||
| ENDS
|
||||
| EXISTS
|
||||
| EXPLAIN
|
||||
| EXTRACT
|
||||
| FALSE
|
||||
| FILTER
|
||||
| IN
|
||||
| INDEX
|
||||
| INFO
|
||||
| IS
|
||||
| LIMIT
|
||||
| L_SKIP
|
||||
@ -337,6 +352,7 @@ cypherKeyword : ALL
|
||||
| SHOW
|
||||
| SINGLE
|
||||
| STARTS
|
||||
| STORAGE
|
||||
| THEN
|
||||
| TRUE
|
||||
| UNION
|
||||
|
@ -74,10 +74,12 @@ ANY : A N Y ;
|
||||
AS : A S ;
|
||||
ASC : A S C ;
|
||||
ASCENDING : A S C E N D I N G ;
|
||||
ASSERT : A S S E R T ;
|
||||
BFS : B F S ;
|
||||
BY : B Y ;
|
||||
CASE : C A S E ;
|
||||
COALESCE : C O A L E S C E ;
|
||||
CONSTRAINT : C O N S T R A I N T ;
|
||||
CONTAINS : C O N T A I N S ;
|
||||
COUNT : C O U N T ;
|
||||
CREATE : C R E A T E ;
|
||||
@ -91,6 +93,7 @@ DROP : D R O P ;
|
||||
ELSE : E L S E ;
|
||||
END : E N D ;
|
||||
ENDS : E N D S ;
|
||||
EXISTS : E X I S T S ;
|
||||
EXPLAIN : E X P L A I N ;
|
||||
EXTRACT : E X T R A C T ;
|
||||
FALSE : F A L S E ;
|
||||
|
@ -30,6 +30,7 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| ROLES
|
||||
| SIZE
|
||||
| START
|
||||
| STATS
|
||||
| STOP
|
||||
| STREAM
|
||||
| STREAMS
|
||||
@ -50,6 +51,7 @@ query : cypherQuery
|
||||
| explainQuery
|
||||
| profileQuery
|
||||
| infoQuery
|
||||
| constraintQuery
|
||||
| authQuery
|
||||
| streamQuery
|
||||
;
|
||||
@ -99,7 +101,7 @@ denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole
|
||||
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ;
|
||||
|
||||
privilege : CREATE | DELETE | MATCH | MERGE | SET
|
||||
| REMOVE | INDEX | STATS | AUTH | STREAM ;
|
||||
| REMOVE | INDEX | STATS | AUTH | STREAM | CONSTRAINT ;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
|
||||
|
@ -40,9 +40,18 @@ class PrivilegeExtractor : public QueryVisitor<void>,
|
||||
case InfoQuery::InfoType::STORAGE:
|
||||
AddPrivilege(AuthQuery::Privilege::STATS);
|
||||
break;
|
||||
case InfoQuery::InfoType::CONSTRAINT:
|
||||
// TODO: This should be CONSTRAINT | STATS, but we don't have support
|
||||
// for *or* with privileges.
|
||||
AddPrivilege(AuthQuery::Privilege::CONSTRAINT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(ConstraintQuery &constraint_query) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CONSTRAINT);
|
||||
}
|
||||
|
||||
void Visit(CypherQuery &query) override {
|
||||
query.single_query_->Accept(*this);
|
||||
for (auto *cypher_union : query.cypher_unions_) {
|
||||
|
@ -91,7 +91,7 @@ const trie::Trie kKeywords = {
|
||||
"stream", "streams", "load", "data", "kafka", "transform",
|
||||
"batch", "interval", "show", "start", "stats", "stop",
|
||||
"size", "topic", "test", "unique", "explain", "profile",
|
||||
"storage", "index", "info"};
|
||||
"storage", "index", "info", "exists" "assert", "constraint"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(std::string(
|
||||
|
@ -613,6 +613,33 @@ Callback HandleInfoQuery(InfoQuery *info_query, database::GraphDbAccessor *db_ac
|
||||
return results;
|
||||
};
|
||||
break;
|
||||
case InfoQuery::InfoType::CONSTRAINT:
|
||||
throw utils::NotYetImplemented("constraint info");
|
||||
break;
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
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.push_back(prop_ix.name);
|
||||
}
|
||||
std::string label_name = constraint_query->label_.name;
|
||||
switch (constraint_query->action_type_) {
|
||||
case ConstraintQuery::ActionType::CREATE:
|
||||
throw utils::NotYetImplemented("create constraint :{}({}) exists",
|
||||
label_name,
|
||||
utils::Join(property_names, ", "));
|
||||
break;
|
||||
case ConstraintQuery::ActionType::DROP:
|
||||
throw utils::NotYetImplemented("drop constraint :{}({}) exists",
|
||||
label_name,
|
||||
utils::Join(property_names, ", "));
|
||||
break;
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
@ -849,8 +876,12 @@ Interpreter::Results Interpreter::operator()(
|
||||
}
|
||||
callback = HandleStreamQuery(stream_query, kafka_streams_, parameters,
|
||||
&db_accessor);
|
||||
} else if (auto *info_query = utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
} else if (auto *info_query =
|
||||
utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
callback = HandleInfoQuery(info_query, &db_accessor);
|
||||
} else if (auto *constraint_query =
|
||||
utils::Downcast<ConstraintQuery>(parsed_query.query)) {
|
||||
callback = HandleConstraintQuery(constraint_query, &db_accessor);
|
||||
} else {
|
||||
LOG(FATAL) << "Should not get here -- unknown query type!";
|
||||
}
|
||||
|
@ -2508,4 +2508,78 @@ TYPED_TEST(CypherMainVisitorTest, TestShowIndexInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestShowConstraintInfo) {
|
||||
{
|
||||
TypeParam ast_generator("SHOW CONSTRAINT INFO");
|
||||
auto *query = dynamic_cast<InfoQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->info_type_, InfoQuery::InfoType::CONSTRAINT);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, CreateConstraintSyntaxError) {
|
||||
EXPECT_THROW(
|
||||
TypeParam ast_generator("CREATE CONSTRAINT ON (:label) ASSERT EXISTS"),
|
||||
SyntaxException);
|
||||
EXPECT_THROW(TypeParam ast_generator("CREATE CONSTRAINT () ASSERT EXISTS"),
|
||||
SyntaxException);
|
||||
EXPECT_THROW(
|
||||
TypeParam ast_generator("CREATE CONSTRAINT ON () ASSERT EXISTS(prop1)"),
|
||||
SyntaxException);
|
||||
EXPECT_THROW(TypeParam ast_generator(
|
||||
"CREATE CONSTRAINT ON () ASSERT EXISTS (prop1, prop2)"),
|
||||
SyntaxException);
|
||||
EXPECT_THROW(TypeParam ast_generator("CREATE CONSTRAINT ON (n:label) ASSERT "
|
||||
"EXISTS (n.prop1, missing.prop2)"),
|
||||
SemanticException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, CreateConstraint) {
|
||||
{
|
||||
TypeParam ast_generator(
|
||||
"CREATE CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1)");
|
||||
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
|
||||
EXPECT_EQ(query->label_, ast_generator.Label("label"));
|
||||
EXPECT_THAT(query->properties_,
|
||||
UnorderedElementsAre(ast_generator.Prop("prop1")));
|
||||
}
|
||||
{
|
||||
TypeParam ast_generator(
|
||||
"CREATE CONSTRAINT ON (n:label) ASSERT EXISTS (n.prop1, n.prop2)");
|
||||
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::CREATE);
|
||||
EXPECT_EQ(query->label_, ast_generator.Label("label"));
|
||||
EXPECT_THAT(query->properties_,
|
||||
UnorderedElementsAre(ast_generator.Prop("prop1"),
|
||||
ast_generator.Prop("prop2")));
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, DropConstraint) {
|
||||
{
|
||||
TypeParam ast_generator(
|
||||
"DROP CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1)");
|
||||
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
|
||||
EXPECT_EQ(query->label_, ast_generator.Label("label"));
|
||||
EXPECT_THAT(query->properties_,
|
||||
UnorderedElementsAre(ast_generator.Prop("prop1")));
|
||||
}
|
||||
{
|
||||
TypeParam ast_generator(
|
||||
"DROP CONSTRAINT ON (n:label) ASSERT EXISTS(n.prop1, n.prop2)");
|
||||
auto *query = dynamic_cast<ConstraintQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_type_, ConstraintQuery::ActionType::DROP);
|
||||
EXPECT_EQ(query->label_, ast_generator.Label("label"));
|
||||
EXPECT_THAT(query->properties_,
|
||||
UnorderedElementsAre(ast_generator.Prop("prop1"),
|
||||
ast_generator.Prop("prop2")));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -147,3 +147,30 @@ TEST_F(TestPrivilegeExtractor, ShowStatsInfo) {
|
||||
UnorderedElementsAre(AuthQuery::Privilege::STATS));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowConstraintInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::CONSTRAINT;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
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"));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
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"));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user