Add clauses for showing DB information
Summary: Also add STATS Privilege and Permission. Update tests and changelog. Reviewers: mtomic, mferencevic, msantl Reviewed By: msantl Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1873
This commit is contained in:
parent
8589dd124d
commit
6bba5f4cd0
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
||||
# Change Log
|
||||
|
||||
## Next Release
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* `indexInfo()` function replaced with `SHOW INDEX INFO` syntax.
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Add new privilege, `STATS` for accessing storage info.
|
||||
* Add `SHOW STORAGE INFO` feature.
|
||||
|
||||
## v0.14.0
|
||||
|
||||
### Breaking Changes
|
||||
|
@ -33,6 +33,8 @@ std::string PermissionToString(Permission permission) {
|
||||
return "REMOVE";
|
||||
case Permission::INDEX:
|
||||
return "INDEX";
|
||||
case Permission::STATS:
|
||||
return "STATS";
|
||||
case Permission::AUTH:
|
||||
return "AUTH";
|
||||
case Permission::STREAM:
|
||||
|
@ -17,6 +17,7 @@ enum class Permission : uint64_t {
|
||||
SET = 0x00000010,
|
||||
REMOVE = 0x00000020,
|
||||
INDEX = 0x00000040,
|
||||
STATS = 0x00000080,
|
||||
AUTH = 0x00010000,
|
||||
STREAM = 0x00020000,
|
||||
};
|
||||
@ -25,7 +26,8 @@ enum class Permission : uint64_t {
|
||||
const std::vector<Permission> kPermissionsAll = {
|
||||
Permission::MATCH, Permission::CREATE, Permission::MERGE,
|
||||
Permission::DELETE, Permission::SET, Permission::REMOVE,
|
||||
Permission::INDEX, Permission::AUTH, Permission::STREAM};
|
||||
Permission::INDEX, Permission::STATS, Permission::AUTH,
|
||||
Permission::STREAM};
|
||||
|
||||
// Function that converts a permission to its string representation.
|
||||
std::string PermissionToString(Permission permission);
|
||||
|
@ -18,6 +18,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
||||
return auth::Permission::REMOVE;
|
||||
case query::AuthQuery::Privilege::INDEX:
|
||||
return auth::Permission::INDEX;
|
||||
case query::AuthQuery::Privilege::STATS:
|
||||
return auth::Permission::STATS;
|
||||
case query::AuthQuery::Privilege::AUTH:
|
||||
return auth::Permission::AUTH;
|
||||
case query::AuthQuery::Privilege::STREAM:
|
||||
|
@ -2402,7 +2402,7 @@ cpp<#
|
||||
show-users-for-role)
|
||||
(:serialize))
|
||||
(lcp:define-enum privilege
|
||||
(create delete match merge set remove index auth stream)
|
||||
(create delete match merge set remove index stats auth stream)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
AuthQuery() = default;
|
||||
@ -2435,8 +2435,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM};
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::STATS,
|
||||
AuthQuery::Privilege::AUTH, AuthQuery::Privilege::STREAM};
|
||||
cpp<#
|
||||
|
||||
(lcp:define-class stream-query (query)
|
||||
@ -2511,4 +2511,17 @@ cpp<#
|
||||
(:serialize (:slk) (:capnp))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class info-query (query)
|
||||
((info-type "InfoType" :scope :public))
|
||||
(:public
|
||||
(lcp:define-enum info-type
|
||||
(storage index)
|
||||
(:serialize))
|
||||
|
||||
#>cpp
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:serialize (:slk) (:capnp))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
|
@ -66,6 +66,7 @@ class ExplainQuery;
|
||||
class ProfileQuery;
|
||||
class IndexQuery;
|
||||
class StreamQuery;
|
||||
class InfoQuery;
|
||||
|
||||
using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator,
|
||||
@ -109,6 +110,6 @@ class ExpressionVisitor
|
||||
template <class TResult>
|
||||
class QueryVisitor
|
||||
: public ::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery,
|
||||
IndexQuery, AuthQuery, StreamQuery> {};
|
||||
IndexQuery, AuthQuery, StreamQuery, InfoQuery> {};
|
||||
|
||||
} // namespace query
|
||||
|
@ -46,6 +46,23 @@ antlrcpp::Any CypherMainVisitor::visitProfileQuery(
|
||||
return profile_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitInfoQuery(
|
||||
MemgraphCypher::InfoQueryContext *ctx) {
|
||||
CHECK(ctx->children.size() == 2)
|
||||
<< "ProfileQuery should have exactly two children!";
|
||||
auto *info_query = storage_->Create<InfoQuery>();
|
||||
query_ = info_query;
|
||||
if (ctx->storageInfo()) {
|
||||
info_query->info_type_ = InfoQuery::InfoType::STORAGE;
|
||||
return info_query;
|
||||
} else if (ctx->indexInfo()) {
|
||||
info_query->info_type_ = InfoQuery::InfoType::INDEX;
|
||||
return info_query;
|
||||
} else {
|
||||
throw utils::NotYetImplemented("Info query: '{}'", ctx->getText());
|
||||
}
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCypherQuery(
|
||||
MemgraphCypher::CypherQueryContext *ctx) {
|
||||
auto *cypher_query = storage_->Create<CypherQuery>();
|
||||
@ -475,6 +492,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(
|
||||
if (ctx->SET()) return AuthQuery::Privilege::SET;
|
||||
if (ctx->REMOVE()) return AuthQuery::Privilege::REMOVE;
|
||||
if (ctx->INDEX()) return AuthQuery::Privilege::INDEX;
|
||||
if (ctx->STATS()) return AuthQuery::Privilege::STATS;
|
||||
if (ctx->AUTH()) return AuthQuery::Privilege::AUTH;
|
||||
if (ctx->STREAM()) return AuthQuery::Privilege::STREAM;
|
||||
LOG(FATAL) << "Should not get here - unknown privilege!";
|
||||
|
@ -159,6 +159,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
antlrcpp::Any visitProfileQuery(
|
||||
MemgraphCypher::ProfileQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return InfoQuery*
|
||||
*/
|
||||
antlrcpp::Any visitInfoQuery(MemgraphCypher::InfoQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
|
@ -27,8 +27,15 @@ query : cypherQuery
|
||||
| indexQuery
|
||||
| explainQuery
|
||||
| profileQuery
|
||||
| infoQuery
|
||||
;
|
||||
|
||||
storageInfo : STORAGE INFO ;
|
||||
|
||||
indexInfo : INDEX INFO ;
|
||||
|
||||
infoQuery : SHOW ( storageInfo | indexInfo ) ;
|
||||
|
||||
explainQuery : EXPLAIN cypherQuery ;
|
||||
|
||||
profileQuery : PROFILE cypherQuery ;
|
||||
|
@ -97,6 +97,7 @@ FALSE : F A L S E ;
|
||||
FILTER : F I L T E R ;
|
||||
IN : I N ;
|
||||
INDEX : I N D E X ;
|
||||
INFO : I N F O ;
|
||||
IS : I S ;
|
||||
LIMIT : L I M I T ;
|
||||
L_SKIP : S K I P ;
|
||||
@ -116,6 +117,7 @@ SET : S E T ;
|
||||
SHOW : S H O W ;
|
||||
SINGLE : S I N G L E ;
|
||||
STARTS : S T A R T S ;
|
||||
STORAGE : S T O R A G E ;
|
||||
THEN : T H E N ;
|
||||
TRUE : T R U E ;
|
||||
UNION : U N I O N ;
|
||||
|
@ -49,6 +49,7 @@ query : cypherQuery
|
||||
| indexQuery
|
||||
| explainQuery
|
||||
| profileQuery
|
||||
| infoQuery
|
||||
| authQuery
|
||||
| streamQuery
|
||||
;
|
||||
@ -98,7 +99,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 | AUTH | STREAM ;
|
||||
| REMOVE | INDEX | STATS | AUTH | STREAM ;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
|
||||
|
@ -34,6 +34,7 @@ ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
SIZE : S I Z E ;
|
||||
START : S T A R T ;
|
||||
STATS : S T A T S ;
|
||||
STOP : S T O P ;
|
||||
STREAM : S T R E A M ;
|
||||
STREAMS : S T R E A M S ;
|
||||
|
@ -30,6 +30,19 @@ class PrivilegeExtractor : public QueryVisitor<void>,
|
||||
query.cypher_query_->Accept(*this);
|
||||
}
|
||||
|
||||
void Visit(InfoQuery &info_query) override {
|
||||
switch (info_query.info_type_) {
|
||||
case InfoQuery::InfoType::INDEX:
|
||||
// TODO: This should be INDEX | STATS, but we don't have support for
|
||||
// *or* with privileges.
|
||||
AddPrivilege(AuthQuery::Privilege::INDEX);
|
||||
break;
|
||||
case InfoQuery::InfoType::STORAGE:
|
||||
AddPrivilege(AuthQuery::Privilege::STATS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(CypherQuery &query) override {
|
||||
query.single_query_->Accept(*this);
|
||||
for (auto *cypher_union : query.cypher_unions_) {
|
||||
|
@ -89,8 +89,9 @@ const trie::Trie kKeywords = {
|
||||
"extract", "any", "none", "single", "true", "false",
|
||||
"reduce", "coalesce", "user", "password", "alter", "drop",
|
||||
"stream", "streams", "load", "data", "kafka", "transform",
|
||||
"batch", "interval", "show", "start", "stop", "size",
|
||||
"topic", "test", "unique", "explain", "profile"};
|
||||
"batch", "interval", "show", "start", "stats", "stop",
|
||||
"size", "topic", "test", "unique", "explain", "profile",
|
||||
"storage", "index", "info"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(std::string(
|
||||
|
@ -675,15 +675,6 @@ TypedValue CounterSet(TypedValue *args, int64_t nargs,
|
||||
return TypedValue::Null;
|
||||
}
|
||||
|
||||
TypedValue IndexInfo(TypedValue *, int64_t nargs, const EvaluationContext &,
|
||||
database::GraphDbAccessor *dba) {
|
||||
if (nargs != 0)
|
||||
throw QueryRuntimeException("'indexInfo' requires no arguments.");
|
||||
|
||||
auto info = dba->IndexInfo();
|
||||
return std::vector<TypedValue>(info.begin(), info.end());
|
||||
}
|
||||
|
||||
#ifdef MG_DISTRIBUTED
|
||||
TypedValue WorkerId(TypedValue *args, int64_t nargs, const EvaluationContext &,
|
||||
database::GraphDbAccessor *) {
|
||||
@ -703,17 +694,6 @@ TypedValue WorkerId(TypedValue *args, int64_t nargs, const EvaluationContext &,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(MG_SINGLE_NODE) || defined(MG_SINGLE_NODE_HA)
|
||||
TypedValue StorageInfo(TypedValue *, int64_t nargs, const EvaluationContext &,
|
||||
database::GraphDbAccessor *dba) {
|
||||
if (nargs != 0)
|
||||
throw QueryRuntimeException("'storageInfo' requires no arguments.");
|
||||
|
||||
auto info = dba->StorageInfo();
|
||||
return std::map<std::string, TypedValue>(info.begin(), info.end());
|
||||
}
|
||||
#endif
|
||||
|
||||
TypedValue Id(TypedValue *args, int64_t nargs, const EvaluationContext &,
|
||||
database::GraphDbAccessor *dba) {
|
||||
if (nargs != 1) {
|
||||
@ -992,13 +972,9 @@ NameToFunction(const std::string &function_name) {
|
||||
if (function_name == "ASSERT") return Assert;
|
||||
if (function_name == "COUNTER") return Counter;
|
||||
if (function_name == "COUNTERSET") return CounterSet;
|
||||
if (function_name == "INDEXINFO") return IndexInfo;
|
||||
#ifdef MG_DISTRIBUTED
|
||||
if (function_name == "WORKERID") return WorkerId;
|
||||
#endif
|
||||
#if defined(MG_SINGLE_NODE) || defined(MG_SINGLE_NODE_HA)
|
||||
if (function_name == "STORAGEINFO") return StorageInfo;
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -582,6 +582,41 @@ Callback HandleIndexQuery(IndexQuery *index_query,
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleInfoQuery(InfoQuery *info_query, database::GraphDbAccessor *db_accessor) {
|
||||
Callback callback;
|
||||
switch (info_query->info_type_) {
|
||||
case InfoQuery::InfoType::STORAGE:
|
||||
#if defined(MG_SINGLE_NODE) || defined(MG_SINGLE_NODE_HA)
|
||||
callback.header = {"storage info", "value"};
|
||||
callback.fn = [db_accessor] {
|
||||
auto info = db_accessor->StorageInfo();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(info.size());
|
||||
for (const auto &kv : info) {
|
||||
results.push_back({kv.first, kv.second});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
#else
|
||||
throw utils::NotYetImplemented("storage info");
|
||||
#endif
|
||||
break;
|
||||
case InfoQuery::InfoType::INDEX:
|
||||
callback.header = {"created index"};
|
||||
callback.fn = [db_accessor] {
|
||||
auto info = db_accessor->IndexInfo();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(info.size());
|
||||
for (const auto &index : info) {
|
||||
results.push_back({index});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
break;
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
Interpreter::Interpreter() : is_tsc_available_(utils::CheckAvailableTSC()) {}
|
||||
|
||||
Interpreter::Results Interpreter::operator()(
|
||||
@ -814,6 +849,8 @@ Interpreter::Results Interpreter::operator()(
|
||||
}
|
||||
callback = HandleStreamQuery(stream_query, kafka_streams_, parameters,
|
||||
&db_accessor);
|
||||
} else if (auto *info_query = utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
callback = HandleInfoQuery(info_query, &db_accessor);
|
||||
} else {
|
||||
LOG(FATAL) << "Should not get here -- unknown query type!";
|
||||
}
|
||||
|
@ -2109,12 +2109,7 @@ TYPED_TEST(CypherMainVisitorTest, RevokePrivilege) {
|
||||
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH});
|
||||
check_auth_query<TypeParam>(
|
||||
"REVOKE ALL PRIVILEGES FROM user", AuthQuery::Action::REVOKE_PRIVILEGE,
|
||||
"", "", "user", {},
|
||||
{AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM});
|
||||
"", "", "user", {}, kPrivilegesAll);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, ShowPrivileges) {
|
||||
|
@ -2167,12 +2167,7 @@ TYPED_TEST(CypherMainVisitorTest, RevokePrivilege) {
|
||||
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH});
|
||||
check_auth_query<TypeParam>(
|
||||
"REVOKE ALL PRIVILEGES FROM user", AuthQuery::Action::REVOKE_PRIVILEGE,
|
||||
"", "", "user", {},
|
||||
{AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM});
|
||||
"", "", "user", {}, kPrivilegesAll);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, ShowPrivileges) {
|
||||
@ -2495,4 +2490,22 @@ TYPED_TEST(CypherMainVisitorTest, TestProfileStreamQuery) {
|
||||
SyntaxException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestShowStorageInfo) {
|
||||
{
|
||||
TypeParam ast_generator("SHOW STORAGE INFO");
|
||||
auto *query = dynamic_cast<InfoQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->info_type_, InfoQuery::InfoType::STORAGE);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestShowIndexInfo) {
|
||||
{
|
||||
TypeParam ast_generator("SHOW INDEX INFO");
|
||||
auto *query = dynamic_cast<InfoQuery *>(ast_generator.query_);
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->info_type_, InfoQuery::InfoType::INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -1438,30 +1438,6 @@ TEST_F(FunctionTest, CounterSet) {
|
||||
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}).ValueInt(), 43);
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, IndexInfo) {
|
||||
EXPECT_THROW(EvaluateFunction("INDEXINFO", {1}), QueryRuntimeException);
|
||||
EXPECT_EQ(EvaluateFunction("INDEXINFO", {}).ValueList().size(), 0);
|
||||
dba->InsertVertex().add_label(dba->Label("l1"));
|
||||
{
|
||||
auto info = ToList<std::string>(EvaluateFunction("INDEXINFO", {}));
|
||||
EXPECT_EQ(info.size(), 1);
|
||||
EXPECT_EQ(info[0], ":l1");
|
||||
}
|
||||
{
|
||||
dba->BuildIndex(dba->Label("l1"), dba->Property("prop"), false);
|
||||
auto info = ToList<std::string>(EvaluateFunction("INDEXINFO", {}));
|
||||
EXPECT_EQ(info.size(), 2);
|
||||
EXPECT_THAT(info, testing::UnorderedElementsAre(":l1", ":l1(prop)"));
|
||||
}
|
||||
{
|
||||
dba->BuildIndex(dba->Label("l1"), dba->Property("prop1"), true);
|
||||
auto info = ToList<std::string>(EvaluateFunction("INDEXINFO", {}));
|
||||
EXPECT_EQ(info.size(), 3);
|
||||
EXPECT_THAT(info, testing::UnorderedElementsAre(":l1", ":l1(prop)",
|
||||
":l1(prop1) unique"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, Id) {
|
||||
auto va = dba->InsertVertex();
|
||||
auto ea = dba->InsertEdge(va, va, dba->EdgeType("edge"));
|
||||
|
@ -132,3 +132,18 @@ TEST_F(TestPrivilegeExtractor, StreamQuery) {
|
||||
UnorderedElementsAre(AuthQuery::Privilege::STREAM));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowIndexInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::INDEX;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::INDEX));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowStatsInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::STORAGE;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::STATS));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user