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:
Teon Banek 2019-02-19 13:31:46 +01:00
parent 8589dd124d
commit 6bba5f4cd0
20 changed files with 159 additions and 68 deletions

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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!";

View File

@ -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*
*/

View File

@ -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 ;

View File

@ -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 ;

View File

@ -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 )* ;

View File

@ -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 ;

View File

@ -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_) {

View File

@ -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(

View File

@ -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;
}

View File

@ -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!";
}

View File

@ -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) {

View File

@ -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

View File

@ -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"));

View File

@ -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));
}