Add parsing and planning of basic user management queries

Reviewers: teon.banek, mferencevic

Reviewed By: teon.banek, mferencevic

Subscribers: pullbot, buda

Differential Revision: https://phabricator.memgraph.io/D1398
This commit is contained in:
Marin Tomic 2018-06-14 16:02:27 +02:00
parent 3948cea83c
commit b9be394cb2
24 changed files with 712 additions and 54 deletions

View File

@ -48,7 +48,7 @@ cpp<#
(symbol-table "query::SymbolTable" :capnp-type "Sem.SymbolTable")
(storage "query::AstStorage" :initarg nil
:save-fun ""
:load-fun "storage = std::move(ar.template get_helper<query::AstTreeStorage>(query::AstTreeStorage::kHelperId));"
:load-fun "storage = std::move(ar.template get_helper<query::AstStorage>(query::AstStorage::kHelperId));"
:capnp-save :dont-save)))
(:response ()))

View File

@ -115,4 +115,11 @@ class RemoveAttachedVertexException : public QueryRuntimeException {
"connections. Consider using DETACH DELETE.") {}
};
class UserModificationInMulticommandTxException : public QueryException {
public:
UserModificationInMulticommandTxException()
: QueryException(
"User modification not allowed in multicommand transactions") {}
};
} // namespace query

View File

@ -131,6 +131,8 @@ struct Clause {
merge @10 :Merge;
unwind @11 :Unwind;
createIndex @12 :CreateIndex;
modifyUser @13 :ModifyUser;
dropUser @14 :DropUser;
}
}
@ -383,3 +385,12 @@ struct CreateIndex {
property @1 :Storage.Common;
}
struct ModifyUser {
username @0 :Text;
password @1 :Tree;
isCreate @2 :Bool;
}
struct DropUser {
usernames @0 :List(Text);
}

View File

@ -1249,6 +1249,14 @@ Clause *Clause::Construct(const capnp::Clause::Reader &reader,
auto with_reader = reader.getWith();
return With::Construct(with_reader, storage);
}
case capnp::Clause::MODIFY_USER: {
auto mu_reader = reader.getModifyUser();
return ModifyUser::Construct(mu_reader, storage);
}
case capnp::Clause::DROP_USER: {
auto du_reader = reader.getDropUser();
return DropUser::Construct(du_reader, storage);
}
}
}
@ -1806,6 +1814,71 @@ With *With::Construct(const capnp::With::Reader &reader,
return storage->Create<With>();
}
// ModifyUser.
void ModifyUser::Save(capnp::Clause::Builder *clause_builder,
std::vector<int> *saved_uids) {
Clause::Save(clause_builder, saved_uids);
auto builder = clause_builder->initModifyUser();
ModifyUser::Save(&builder, saved_uids);
}
void ModifyUser::Save(capnp::ModifyUser::Builder *builder,
std::vector<int> *saved_uids) {
builder->setUsername(username_);
if (password_) {
auto password_builder = builder->getPassword();
password_->Save(&password_builder, saved_uids);
}
builder->setIsCreate(is_create_);
}
void ModifyUser::Load(const capnp::Tree::Reader &base_reader,
AstStorage *storage, std::vector<int> *loaded_uids) {
Clause::Load(base_reader, storage, loaded_uids);
auto reader = base_reader.getClause().getModifyUser();
username_ = reader.getUsername();
if (reader.hasPassword()) {
const auto password_reader = reader.getPassword();
password_ =
dynamic_cast<Expression *>(storage->Load(password_reader, loaded_uids));
} else {
password_ = nullptr;
}
is_create_ = reader.getIsCreate();
}
ModifyUser *ModifyUser::Construct(const capnp::ModifyUser::Reader &reader,
AstStorage *storage) {
return storage->Create<ModifyUser>();
}
// DropUser.
void DropUser::Save(capnp::Clause::Builder *clause_builder,
std::vector<int> *saved_uids) {
Clause::Save(clause_builder, saved_uids);
auto builder = clause_builder->initDropUser();
DropUser::Save(&builder, saved_uids);
}
void DropUser::Save(capnp::DropUser::Builder *builder,
std::vector<int> *saved_uids) {
auto usernames_builder = builder->initUsernames(usernames_.size());
utils::SaveVector(usernames_, &usernames_builder);
}
void DropUser::Load(const capnp::Tree::Reader &base_reader,
AstStorage *storage, std::vector<int> *loaded_uids) {
Clause::Load(base_reader, storage, loaded_uids);
auto reader = base_reader.getClause().getDropUser();
usernames_.clear();
utils::LoadVector(&usernames_, reader.getUsernames());
}
DropUser *DropUser::Construct(const capnp::DropUser::Reader &reader,
AstStorage *storage) {
return storage->Create<DropUser>();
}
// CypherUnion
void CypherUnion::Save(capnp::Tree::Builder *tree_builder,
std::vector<int> *saved_uids) {
@ -2367,3 +2440,5 @@ BOOST_CLASS_EXPORT_IMPLEMENT(query::Unwind);
BOOST_CLASS_EXPORT_IMPLEMENT(query::Identifier);
BOOST_CLASS_EXPORT_IMPLEMENT(query::PrimitiveLiteral);
BOOST_CLASS_EXPORT_IMPLEMENT(query::CreateIndex);
BOOST_CLASS_EXPORT_IMPLEMENT(query::ModifyUser);
BOOST_CLASS_EXPORT_IMPLEMENT(query::DropUser);

View File

@ -3499,6 +3499,100 @@ class CreateIndex : public Clause {
const unsigned int);
};
class ModifyUser : public Clause {
friend class AstStorage;
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
DEFVISITABLE(HierarchicalTreeVisitor);
ModifyUser *Clone(AstStorage &storage) const override {
return storage.Create<ModifyUser>(
username_, password_ ? password_->Clone(storage) : nullptr, is_create_);
}
static ModifyUser *Construct(const capnp::ModifyUser::Reader &reader,
AstStorage *storage);
using Clause::Save;
std::string username_;
Expression *password_;
bool is_create_;
protected:
explicit ModifyUser(int uid) : Clause(uid) {}
ModifyUser(int uid, std::string username, Expression *password,
bool is_create)
: Clause(uid),
username_(std::move(username)),
password_(password),
is_create_(is_create) {}
void Save(capnp::Clause::Builder *builder,
std::vector<int> *saved_uids) override;
virtual void Save(capnp::ModifyUser::Builder *builder,
std::vector<int> *saved_uids);
void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage,
std::vector<int> *loaded_uids) override;
private:
friend class boost::serialization::access;
template <class TArchive>
void serialize(TArchive &ar, const unsigned int) {
ar &boost::serialization::base_object<Clause>(*this);
ar &username_ &password_ &is_create_;
}
template <class TArchive>
friend void boost::serialization::load_construct_data(TArchive &,
ModifyUser *,
const unsigned int);
};
class DropUser : public Clause {
friend class AstStorage;
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
DEFVISITABLE(HierarchicalTreeVisitor);
DropUser *Clone(AstStorage &storage) const override {
return storage.Create<DropUser>(usernames_);
}
static DropUser *Construct(const capnp::DropUser::Reader &reader,
AstStorage *storage);
using Clause::Save;
std::vector<std::string> usernames_;
protected:
explicit DropUser(int uid) : Clause(uid) {}
DropUser(int uid, std::vector<std::string> usernames)
: Clause(uid), usernames_(usernames) {}
void Save(capnp::Clause::Builder *builder,
std::vector<int> *saved_uids) override;
virtual void Save(capnp::DropUser::Builder *builder,
std::vector<int> *saved_uids);
void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage,
std::vector<int> *loaded_uids) override;
private:
friend class boost::serialization::access;
template <class TArchive>
void serialize(TArchive &ar, const unsigned int) {
ar &boost::serialization::base_object<Clause>(*this);
ar &usernames_;
}
template <class TArchive>
friend void boost::serialization::load_construct_data(TArchive &, DropUser *,
const unsigned int);
};
#undef CLONE_BINARY_EXPRESSION
#undef CLONE_UNARY_EXPRESSION
#undef SERIALIZE_USING_BASE
@ -3573,6 +3667,8 @@ LOAD_AND_CONSTRUCT(query::RemoveLabels, 0);
LOAD_AND_CONSTRUCT(query::Merge, 0);
LOAD_AND_CONSTRUCT(query::Unwind, 0);
LOAD_AND_CONSTRUCT(query::CreateIndex, 0);
LOAD_AND_CONSTRUCT(query::ModifyUser, 0);
LOAD_AND_CONSTRUCT(query::DropUser, 0);
} // namespace boost::serialization
@ -3633,3 +3729,5 @@ BOOST_CLASS_EXPORT_KEY(query::Unwind);
BOOST_CLASS_EXPORT_KEY(query::Identifier);
BOOST_CLASS_EXPORT_KEY(query::PrimitiveLiteral);
BOOST_CLASS_EXPORT_KEY(query::CreateIndex);
BOOST_CLASS_EXPORT_KEY(query::ModifyUser);
BOOST_CLASS_EXPORT_KEY(query::DropUser);

View File

@ -60,6 +60,8 @@ class RemoveLabels;
class Merge;
class Unwind;
class CreateIndex;
class ModifyUser;
class DropUser;
using TreeCompositeVisitor = ::utils::CompositeVisitor<
Query, SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator,
@ -73,8 +75,9 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties,
SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind>;
using TreeLeafVisitor = ::utils::LeafVisitor<Identifier, PrimitiveLiteral,
ParameterLookup, CreateIndex>;
using TreeLeafVisitor =
::utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup,
CreateIndex, ModifyUser, DropUser>;
class HierarchicalTreeVisitor : public TreeCompositeVisitor,
public TreeLeafVisitor {
@ -97,6 +100,6 @@ using TreeVisitor = ::utils::Visitor<
LabelsTest, Aggregation, Function, Reduce, All, Single, ParameterLookup,
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where,
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge,
Unwind, Identifier, PrimitiveLiteral, CreateIndex>;
Unwind, Identifier, PrimitiveLiteral, CreateIndex, ModifyUser, DropUser>;
} // namespace query

View File

@ -81,6 +81,7 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
bool has_return = false;
bool has_optional_match = false;
bool has_create_index = false;
bool has_modify_user = false;
for (Clause *clause : single_query->clauses_) {
if (dynamic_cast<Unwind *>(clause)) {
if (has_update || has_return) {
@ -125,11 +126,21 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
"CreateIndex must be only clause in the query.");
}
has_create_index = true;
} else if (dynamic_cast<ModifyUser *>(clause)) {
has_modify_user = true;
if (single_query->clauses_.size() != 1U) {
throw SemanticException("ModifyUser must be only clause in the query.");
}
} else if (dynamic_cast<DropUser *>(clause)) {
has_modify_user = true;
if (single_query->clauses_.size() != 1U) {
throw SemanticException("DropUser must be only clause in the query.");
}
} else {
DLOG(FATAL) << "Can't happen";
}
}
if (!has_update && !has_return && !has_create_index) {
if (!has_update && !has_return && !has_create_index && !has_modify_user) {
throw SemanticException(
"Query should either update something, return results or create an "
"index");
@ -186,6 +197,14 @@ antlrcpp::Any CypherMainVisitor::visitClause(CypherParser::ClauseContext *ctx) {
return static_cast<Clause *>(
ctx->createIndex()->accept(this).as<CreateIndex *>());
}
if (ctx->modifyUser()) {
return static_cast<Clause *>(
ctx->modifyUser()->accept(this).as<ModifyUser *>());
}
if (ctx->dropUser()) {
return static_cast<Clause *>(
ctx->dropUser()->accept(this).as<DropUser *>());
}
// TODO: implement other clauses.
throw utils::NotYetImplemented("clause '{}'", ctx->getText());
return 0;
@ -219,6 +238,49 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex(
ctx_.db_accessor_.Label(ctx->labelName()->accept(this)), key.second);
}
/**
* @return ModifyUser*
*/
antlrcpp::Any CypherMainVisitor::visitModifyUser(
CypherParser::ModifyUserContext *ctx) {
std::string username(ctx->userName()->getText());
Expression *password = nullptr;
bool is_create = static_cast<bool>(ctx->createUser());
for (auto option : ctx->modifyUserOption()) {
if (option->passwordOption()) {
if (password) {
throw QueryException("password should be set at most once");
}
password = option->passwordOption()->accept(this);
continue;
}
LOG(FATAL) << "Expected to handle all cases above.";
}
return storage_.Create<ModifyUser>(username, password, is_create);
}
/**
* @return Expression*
*/
antlrcpp::Any CypherMainVisitor::visitPasswordOption(
CypherParser::PasswordOptionContext *ctx) {
if (!ctx->literal()->StringLiteral() && !ctx->literal()->CYPHERNULL()) {
throw SyntaxException("password should be a string literal or NULL");
}
return ctx->literal()->accept(this);
}
/**
* @return DropUser*
*/
antlrcpp::Any CypherMainVisitor::visitDropUser(
CypherParser::DropUserContext *ctx) {
std::vector<std::string> usernames;
for (auto username_ptr : ctx->userName())
usernames.emplace_back(username_ptr->getText());
return storage_.Create<DropUser>(usernames);
}
antlrcpp::Any CypherMainVisitor::visitCypherReturn(
CypherParser::CypherReturnContext *ctx) {
auto *return_clause = storage_.Create<Return>();
@ -949,10 +1011,11 @@ antlrcpp::Any CypherMainVisitor::visitLiteral(
return static_cast<Expression *>(
storage_.Create<PrimitiveLiteral>(TypedValue::Null, token_position));
} else if (ctx_.is_query_cached_) {
// Instead of generating PrimitiveLiteral, we generate a ParameterLookup,
// so that the AST can be cached. This allows for varying literals, which
// are then looked up in the parameters table (even though they are not
// user provided). Note, that NULL always generates a PrimitiveLiteral.
// Instead of generating PrimitiveLiteral, we generate a
// ParameterLookup, so that the AST can be cached. This allows for
// varying literals, which are then looked up in the parameters table
// (even though they are not user provided). Note, that NULL always
// generates a PrimitiveLiteral.
return static_cast<Expression *>(
storage_.Create<ParameterLookup>(token_position));
} else if (ctx->StringLiteral()) {

View File

@ -173,6 +173,19 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
antlrcpp::Any visitCreateIndex(
CypherParser::CreateIndexContext *ctx) override;
/**
* @return ModifyUser*
*/
antlrcpp::Any visitModifyUser(CypherParser::ModifyUserContext *ctx) override;
antlrcpp::Any visitPasswordOption(
CypherParser::PasswordOptionContext *ctx) override;
/**
* @return DropUser*
*/
antlrcpp::Any visitDropUser(CypherParser::DropUserContext *ctx) override;
/**
* @return Return*
*/

View File

@ -47,6 +47,8 @@ clause : cypherMatch
| with
| cypherReturn
| createIndex
| modifyUser
| dropUser
;
cypherMatch : ( OPTIONAL SP )? MATCH SP? pattern ( SP? where )? ;
@ -274,6 +276,20 @@ integerLiteral : HexInteger
createIndex : CREATE SP INDEX SP ON SP? ':' SP? labelName SP? '(' SP? propertyKeyName SP? ')' ;
userName : UnescapedSymbolicName ;
createUser : CREATE SP USER ;
alterUser : ALTER SP USER ;
modifyUser : ( createUser | alterUser ) SP userName ( SP WITH ( SP modifyUserOption )+ )? ;
modifyUserOption : passwordOption ;
passwordOption : PASSWORD SP literal;
dropUser : DROP SP USER SP userName ( SP? ',' SP? userName )* ;
HexInteger : '0x' ( HexDigit )+ ;
DecimalInteger : ZeroDigit
@ -484,6 +500,14 @@ BFS : ( 'B' | 'b' ) ( 'F' | 'f' ) ( 'S' | 's' ) ;
WSHORTEST : ( 'W' | 'w' ) ( 'S' | 's' ) ( 'H' | 'h' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ( 'T' | 't' ) ( 'E' | 'e' ) ( 'S' | 's' ) ( 'T' | 't' ) ;
USER : ( 'U' | 'u' ) ( 'S' | 's' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ;
PASSWORD : ( 'P' | 'p' ) ( 'A' | 'a' ) ( 'S' | 's' ) ( 'S' | 's' ) ( 'W' | 'w' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ( 'D' | 'd' ) ;
ALTER : ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'T' | 't' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ;
DROP : ( 'D' | 'd' ) ( 'R' | 'r' ) ( 'O' | 'o' ) ( 'P' | 'p' ) ;
UnescapedSymbolicName : IdentifierStart ( IdentifierPart )* ;
/**
@ -620,4 +644,3 @@ fragment VT : [\u000B] ;
fragment US : [\u001F] ;
fragment ID_Start : [A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58-\u0C59\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC] ;

View File

@ -220,6 +220,10 @@ bool SymbolGenerator::PostVisit(Match &) {
bool SymbolGenerator::Visit(CreateIndex &) { return true; }
bool SymbolGenerator::Visit(ModifyUser &) { return true; }
bool SymbolGenerator::Visit(DropUser &) { return true; }
// Expressions
SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {

View File

@ -47,6 +47,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool PreVisit(Match &) override;
bool PostVisit(Match &) override;
bool Visit(CreateIndex &) override;
bool Visit(ModifyUser &) override;
bool Visit(DropUser &) override;
// Expressions
ReturnType Visit(Identifier &) override;

View File

@ -56,6 +56,8 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
BLOCK_VISIT(Merge);
BLOCK_VISIT(Unwind);
BLOCK_VISIT(CreateIndex);
BLOCK_VISIT(ModifyUser);
BLOCK_VISIT(DropUser);
#undef BLOCK_VISIT

View File

@ -62,8 +62,8 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
static constexpr double kUnwindNoLiteral{10.0};
};
using HierarchicalLogicalOperatorVisitor::PreVisit;
using HierarchicalLogicalOperatorVisitor::PostVisit;
using HierarchicalLogicalOperatorVisitor::PreVisit;
CostEstimator(const TDbAccessor &db_accessor, const Parameters &parameters)
: db_accessor_(db_accessor), parameters(parameters) {}
@ -185,6 +185,8 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
bool Visit(Once &) override { return true; }
bool Visit(CreateIndex &) override { return true; }
bool Visit(ModifyUser &) override { return true; }
bool Visit(DropUser &) override { return true; }
// TODO: Cost estimate PullRemote and ProduceRemote?

View File

@ -55,6 +55,8 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
// These don't use any symbols
bool Visit(Once &) override { return true; }
bool Visit(CreateIndex &) override { return true; }
bool Visit(ModifyUser &) override { return true; }
bool Visit(DropUser &) override { return true; }
bool PostVisit(ScanAll &scan) override { return true; }
bool PostVisit(ScanAllByLabel &scan) override { return true; }
@ -683,6 +685,10 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
bool Visit(CreateIndex &) override { return true; }
bool Visit(ModifyUser &) override { return true; }
bool Visit(DropUser &) override { return true; }
// Accumulate is used only if the query performs any writes. In such a case,
// we need to synchronize the work done on master and all workers.
// Synchronization will force applying changes to distributed storage, and

View File

@ -413,32 +413,32 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
-> std::experimental::optional<decltype(
db.Vertices(label_, property_, std::experimental::nullopt,
std::experimental::nullopt, false))> {
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db, graph_view_);
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
auto value = bound->value()->Accept(evaluator);
try {
return std::experimental::make_optional(
utils::Bound<PropertyValue>(value, bound->type()));
} catch (const TypedValueException &) {
throw QueryRuntimeException(
"'{}' cannot be used as a property value.", value.type());
}
};
auto maybe_lower = convert(lower_bound());
auto maybe_upper = convert(upper_bound());
// If any bound is null, then the comparison would result in nulls. This
// is treated as not satisfying the filter, so return no vertices.
if (maybe_lower && maybe_lower->value().IsNull())
return std::experimental::nullopt;
if (maybe_upper && maybe_upper->value().IsNull())
return std::experimental::nullopt;
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db, graph_view_);
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
auto value = bound->value()->Accept(evaluator);
try {
return std::experimental::make_optional(
db.Vertices(label_, property_, maybe_lower, maybe_upper,
graph_view_ == GraphView::NEW));
};
utils::Bound<PropertyValue>(value, bound->type()));
} catch (const TypedValueException &) {
throw QueryRuntimeException("'{}' cannot be used as a property value.",
value.type());
}
};
auto maybe_lower = convert(lower_bound());
auto maybe_upper = convert(upper_bound());
// If any bound is null, then the comparison would result in nulls. This
// is treated as not satisfying the filter, so return no vertices.
if (maybe_lower && maybe_lower->value().IsNull())
return std::experimental::nullopt;
if (maybe_upper && maybe_upper->value().IsNull())
return std::experimental::nullopt;
return std::experimental::make_optional(
db.Vertices(label_, property_, maybe_lower, maybe_upper,
graph_view_ == GraphView::NEW));
};
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
output_symbol_, input_->MakeCursor(db), std::move(vertices), db);
}
@ -461,18 +461,18 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyValue::MakeCursor(
auto vertices = [this, &db](Frame &frame, Context &context)
-> std::experimental::optional<decltype(
db.Vertices(label_, property_, TypedValue::Null, false))> {
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db, graph_view_);
auto value = expression_->Accept(evaluator);
if (value.IsNull()) return std::experimental::nullopt;
try {
return std::experimental::make_optional(db.Vertices(
label_, property_, value, graph_view_ == GraphView::NEW));
} catch (const TypedValueException &) {
throw QueryRuntimeException(
"'{}' cannot be used as a property value.", value.type());
}
};
ExpressionEvaluator evaluator(frame, context.parameters_,
context.symbol_table_, db, graph_view_);
auto value = expression_->Accept(evaluator);
if (value.IsNull()) return std::experimental::nullopt;
try {
return std::experimental::make_optional(
db.Vertices(label_, property_, value, graph_view_ == GraphView::NEW));
} catch (const TypedValueException &) {
throw QueryRuntimeException("'{}' cannot be used as a property value.",
value.type());
}
};
return std::make_unique<ScanAllCursor<decltype(vertices)>>(
output_symbol_, input_->MakeCursor(db), std::move(vertices), db);
}
@ -1368,7 +1368,8 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
// For the given (edge, vertex, weight, depth) tuple checks if they
// satisfy the "where" condition. if so, places them in the priority queue.
auto expand_pair = [this, &evaluator, &frame, &create_state](
EdgeAccessor edge, VertexAccessor vertex, double weight, int depth) {
EdgeAccessor edge, VertexAccessor vertex,
double weight, int depth) {
SwitchAccessor(edge, self_.graph_view_);
SwitchAccessor(vertex, self_.graph_view_);
@ -3827,6 +3828,74 @@ std::unique_ptr<Cursor> PullRemoteOrderBy::MakeCursor(
return std::make_unique<PullRemoteOrderByCursor>(*this, db);
}
ModifyUser::ModifyUser(std::string username, Expression *password,
bool is_create)
: username_(std::move(username)),
password_(password),
is_create_(is_create) {}
bool ModifyUser::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
return visitor.Visit(*this);
}
WITHOUT_SINGLE_INPUT(ModifyUser)
class ModifyUserCursor : public Cursor {
public:
ModifyUserCursor(const ModifyUser &self, database::GraphDbAccessor &db)
: self_(self), db_(db) {}
bool Pull(Frame &frame, Context &ctx) override {
if (ctx.in_explicit_transaction_) {
throw UserModificationInMulticommandTxException();
}
ExpressionEvaluator evaluator(frame, ctx.parameters_, ctx.symbol_table_,
db_, GraphView::OLD);
throw utils::NotYetImplemented("user auth");
}
void Reset() override { throw utils::NotYetImplemented("user auth"); }
private:
const ModifyUser &self_;
database::GraphDbAccessor &db_;
};
std::unique_ptr<Cursor> ModifyUser::MakeCursor(
database::GraphDbAccessor &db) const {
return std::make_unique<ModifyUserCursor>(*this, db);
}
bool DropUser::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
return visitor.Visit(*this);
}
WITHOUT_SINGLE_INPUT(DropUser)
class DropUserCursor : public Cursor {
public:
DropUserCursor(const DropUser &self, database::GraphDbAccessor &db)
: self_(self), db_(db) {}
bool Pull(Frame &, Context &ctx) override {
if (ctx.in_explicit_transaction_) {
throw UserModificationInMulticommandTxException();
}
throw utils::NotYetImplemented("user auth");
}
void Reset() override { throw utils::NotYetImplemented("user auth"); }
private:
const DropUser &self_;
database::GraphDbAccessor &db_;
};
std::unique_ptr<Cursor> DropUser::MakeCursor(
database::GraphDbAccessor &db) const {
return std::make_unique<DropUserCursor>(*this, db);
}
} // namespace query::plan
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::Once);
@ -3865,3 +3934,5 @@ BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::PullRemote);
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::Synchronize);
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::Cartesian);
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::PullRemoteOrderBy);
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::ModifyUser);
BOOST_CLASS_EXPORT_IMPLEMENT(query::plan::DropUser);

View File

@ -109,6 +109,8 @@ class PullRemote;
class Synchronize;
class Cartesian;
class PullRemoteOrderBy;
class ModifyUser;
class DropUser;
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
@ -120,7 +122,8 @@ using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
OrderBy, Merge, Optional, Unwind, Distinct, Union, PullRemote, Synchronize,
Cartesian, PullRemoteOrderBy>;
using LogicalOperatorLeafVisitor = ::utils::LeafVisitor<Once, CreateIndex>;
using LogicalOperatorLeafVisitor =
::utils::LeafVisitor<Once, CreateIndex, ModifyUser, DropUser>;
/**
* @brief Base class for hierarhical visitors of @c LogicalOperator class
@ -2435,6 +2438,84 @@ by having only one result from each worker.")
(:private #>cpp PullRemoteOrderBy() {} cpp<#)
(:serialize :boost :capnp))
(lcp:define-class modify-user (logical-operator)
((input "std::shared_ptr<LogicalOperator>"
:capnp-save #'save-operator-pointer
:capnp-load #'load-operator-pointer)
(username "std::string" :reader t)
(password "Expression *"
:reader t
:save-fun #'save-pointer
:load-fun #'load-pointer
:capnp-type "Ast.Tree"
:capnp-init nil
:capnp-save #'save-ast-pointer
:capnp-load (load-ast-pointer "Expression *"))
(is-create :bool :reader t))
(:documentation
"Operator that creates a new database user or modifies an existing one.")
(:public
#>cpp
ModifyUser(std::string username, Expression *password, bool is_create);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
std::unique_ptr<Cursor> MakeCursor(
database::GraphDbAccessor &db) const override;
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override {
return std::vector<Symbol>();
}
bool HasSingleInput() const override;
std::shared_ptr<LogicalOperator> input() const override;
void set_input(std::shared_ptr<LogicalOperator> input) override;
cpp<#)
(:private
#>cpp
friend class boost::serialization::access;
ModifyUser() {}
cpp<#)
(:serialize :boost :capnp))
(lcp:define-class drop-user (logical-operator)
((input "std::shared_ptr<LogicalOperator>"
:capnp-save #'save-operator-pointer
:capnp-load #'load-operator-pointer)
(usernames "std::vector<std::string>" :reader t
:capnp-save (lambda (builder member-name)
#>cpp
utils::SaveVector(${member-name}, &${builder});
cpp<#)
:capnp-load (lambda (reader member-name)
#>cpp
utils::LoadVector(&${member-name}, ${reader});
cpp<#)))
(:documentation
"Operator that deletes one or more existing database users.")
(:public
#>cpp
DropUser(std::vector<std::string> usernames): usernames_(usernames) {}
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
std::unique_ptr<Cursor> MakeCursor(
database::GraphDbAccessor &db) const override;
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override {
return std::vector<Symbol>();
}
bool HasSingleInput() const override;
std::shared_ptr<LogicalOperator> input() const override;
void set_input(std::shared_ptr<LogicalOperator> input) override;
cpp<#)
(:private
#>cpp
friend class boost::serialization::access;
DropUser() {}
cpp<#)
(:serialize :boost :capnp))
(lcp:pop-namespace) ;; plan
(lcp:pop-namespace) ;; query
@ -2474,4 +2555,6 @@ BOOST_CLASS_EXPORT_KEY(query::plan::PullRemote);
BOOST_CLASS_EXPORT_KEY(query::plan::Synchronize);
BOOST_CLASS_EXPORT_KEY(query::plan::Cartesian);
BOOST_CLASS_EXPORT_KEY(query::plan::PullRemoteOrderBy);
BOOST_CLASS_EXPORT_KEY(query::plan::ModifyUser);
BOOST_CLASS_EXPORT_KEY(query::plan::DropUser);
cpp<#

View File

@ -53,6 +53,8 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
bool Visit(PrimitiveLiteral &) override { return true; }
bool Visit(ParameterLookup &) override { return true; }
bool Visit(query::CreateIndex &) override { return true; }
bool Visit(query::ModifyUser &) override { return true; }
bool Visit(query::DropUser &) override { return true; }
std::unordered_set<Symbol> symbols_;
const SymbolTable &symbol_table_;

View File

@ -387,6 +387,16 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
return true;
}
bool Visit(query::ModifyUser &) override {
has_aggregation_.emplace_back(false);
return true;
}
bool Visit(query::DropUser &) override {
has_aggregation_.emplace_back(false);
return true;
}
// Creates NamedExpression with an Identifier for each user declared symbol.
// This should be used when body.all_identifiers is true, to generate
// expressions for Produce operator.

View File

@ -182,6 +182,15 @@ class RuleBasedPlanner {
DCHECK(!input_op) << "Unexpected operator before CreateIndex";
input_op = std::make_unique<plan::CreateIndex>(
create_index->label_, create_index->property_);
} else if (auto *modify_user =
dynamic_cast<query::ModifyUser *>(clause)) {
DCHECK(!input_op) << "Unexpected operator before ModifyUser";
input_op = std::make_unique<plan::ModifyUser>(
modify_user->username_, modify_user->password_,
modify_user->is_create_);
} else if (auto *drop_user = dynamic_cast<query::DropUser *>(clause)) {
DCHECK(!input_op) << "Unexpected operator before DropUser";
input_op = std::make_unique<plan::DropUser>(drop_user->usernames_);
} else {
throw utils::NotYetImplemented("clause conversion to operator(s)");
}

View File

@ -185,6 +185,13 @@ inline void SaveVector(const std::vector<T> &data,
}
}
inline void SaveVector(const std::vector<std::string> &data,
::capnp::List<::capnp::Text>::Builder *list_builder) {
for (size_t i = 0; i < data.size(); ++i) {
list_builder->set(i, data[i]);
}
}
template <typename T>
inline void LoadVector(std::vector<T> *data,
const typename ::capnp::List<T>::Reader &list_reader) {
@ -193,6 +200,13 @@ inline void LoadVector(std::vector<T> *data,
}
}
inline void LoadVector(
std::vector<std::string> *data,
const typename ::capnp::List<::capnp::Text>::Reader &list_reader) {
for (const auto e : list_reader) {
data->emplace_back(e); }
}
template <typename TCapnp, typename T>
inline void SaveVector(
const std::vector<T> &data,

View File

@ -514,6 +514,16 @@ class PlanPrinter : public query::plan::HierarchicalLogicalOperatorVisitor {
return true;
}
bool Visit(query::plan::ModifyUser &op) override {
WithPrintLn([](auto &out) { out << "* ModifyUser "; });
return true;
}
bool Visit(query::plan::DropUser &op) override {
WithPrintLn([](auto &out) { out << "* DropUser"; });
return true;
}
bool PreVisit(query::plan::PullRemote &op) override {
WithPrintLn([&op](auto &out) {
out << "* PullRemote [" << op.plan_id() << "] {";

View File

@ -1858,4 +1858,72 @@ TYPED_TEST(CypherMainVisitorTest, UnionAll) {
ASSERT_FALSE(return_clause->body_.distinct);
}
TYPED_TEST(CypherMainVisitorTest, ModifyUser) {
auto check_modify_user = [](std::string input, std::string username,
std::experimental::optional<TypedValue> password,
bool is_create) {
TypeParam ast_generator(input);
auto *query = ast_generator.query_;
ASSERT_TRUE(query->single_query_);
auto *single_query = query->single_query_;
ASSERT_EQ(single_query->clauses_.size(), 1U);
auto *create_user = dynamic_cast<ModifyUser *>(single_query->clauses_[0]);
ASSERT_TRUE(create_user);
EXPECT_EQ(create_user->username_, username);
if (password) {
ASSERT_NE(create_user->password_, nullptr);
CheckLiteral(ast_generator.context_, create_user->password_, *password);
} else {
EXPECT_EQ(create_user->password_, nullptr);
}
EXPECT_EQ(create_user->is_create_, is_create);
};
check_modify_user("CreaTE UsEr dominik", "dominik",
std::experimental::nullopt, true);
check_modify_user("CreaTE UsEr dominik WIth PaSSWORD 'spomenik'", "dominik",
"spomenik", true);
check_modify_user("CreaTE UsEr dominik WIth PaSSWORD NULL", "dominik",
TypedValue::Null, true);
check_modify_user("AlTeR UsEr dominik", "dominik", std::experimental::nullopt,
false);
check_modify_user("ALtEr UsEr dominik", "dominik", std::experimental::nullopt,
false);
check_modify_user("ALtEr UsEr dominik WIth PaSSWORD 'spomenik'", "dominik",
"spomenik", false);
check_modify_user("ALtEr UsEr dominik WIth PaSSWORD NULL", "dominik",
TypedValue::Null, false);
EXPECT_THROW(
check_modify_user(
"CreaTE UsEr dominik WIth PaSSWORD 'spomenik' PaSSwoRD 'u muzeju'",
"dominik", "spomenik", true),
QueryException);
EXPECT_THROW(check_modify_user("CreaTE UsEr dominik WIth PaSSWORD 12345",
"dominik", "spomenik", true),
SyntaxException);
}
TYPED_TEST(CypherMainVisitorTest, DropUser) {
auto check_drop_user = [](std::string input,
const std::vector<std::string> &usernames) {
TypeParam ast_generator(input);
auto *query = ast_generator.query_;
ASSERT_TRUE(query->single_query_);
auto *single_query = query->single_query_;
ASSERT_EQ(single_query->clauses_.size(), 1U);
auto *drop_user = dynamic_cast<DropUser *>(single_query->clauses_[0]);
ASSERT_TRUE(drop_user);
EXPECT_EQ(drop_user->usernames_, usernames);
};
EXPECT_THROW(check_drop_user("DrOp USER", {}), SyntaxException);
check_drop_user("DrOP UsEr dominik", {"dominik"});
check_drop_user("DrOP USER dominik , spomenik", {"dominik", "spomenik"});
EXPECT_THROW(
check_drop_user("DrOP USER dominik, , spomenik", {"dominik", "spomenik"}),
SyntaxException);
check_drop_user("DrOP USER dominik , spomenik , jackie, jackie , johnny",
{"dominik", "spomenik", "jackie", "jackie", "johnny"});
}
} // namespace

View File

@ -607,3 +607,8 @@ auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match,
storage.Create<query::Reduce>( \
storage.Create<query::Identifier>(accumulator), initializer, \
storage.Create<query::Identifier>(variable), list, expr)
#define CREATE_USER(username, password) \
storage.Create<query::ModifyUser>((username), LITERAL(password), true)
#define ALTER_USER(username, password) \
storage.Create<query::ModifyUser>((username), LITERAL(password), false)
#define DROP_USER(usernames) storage.Create<query::DropUser>((usernames))

View File

@ -29,6 +29,8 @@ namespace query {
}
} // namespace query
using std::string_literals::operator""s;
using namespace query::plan;
using query::AstStorage;
using query::SingleQuery;
@ -69,6 +71,12 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
return true; \
}
#define VISIT(TOp) \
bool Visit(TOp &op) override { \
CheckOp(op); \
return true; \
}
PRE_VISIT(CreateNode);
PRE_VISIT(CreateExpand);
PRE_VISIT(Delete);
@ -111,10 +119,7 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
return true;
}
bool Visit(CreateIndex &op) override {
CheckOp(op);
return true;
}
VISIT(CreateIndex);
PRE_VISIT(PullRemote);
@ -130,7 +135,11 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
}
PRE_VISIT(PullRemoteOrderBy);
VISIT(ModifyUser);
VISIT(DropUser);
#undef PRE_VISIT
#undef VISIT
std::list<BaseOpChecker *> checkers_;
@ -484,6 +493,37 @@ class Planner {
std::unique_ptr<LogicalOperator> plan_;
};
class ExpectModifyUser : public OpChecker<ModifyUser> {
public:
ExpectModifyUser(std::string username, bool is_create)
: username_(username), is_create_(is_create) {}
void ExpectOp(ModifyUser &modify_user, const SymbolTable &) override {
EXPECT_EQ(username_, modify_user.username());
// TODO(mtomic): proper password verification
EXPECT_NE(dynamic_cast<query::Expression *>(modify_user.password()),
nullptr);
EXPECT_EQ(is_create_, modify_user.is_create());
}
private:
std::string username_;
query::Expression *password_;
bool is_create_;
};
class ExpectDropUser : public OpChecker<DropUser> {
public:
ExpectDropUser(std::vector<std::string> usernames) : usernames_(usernames) {}
void ExpectOp(DropUser &drop_user, const SymbolTable &) override {
EXPECT_EQ(usernames_, drop_user.usernames());
}
private:
std::vector<std::string> usernames_;
};
class SerializedPlanner {
public:
SerializedPlanner(std::vector<SingleQueryPart> single_query_parts,
@ -2173,6 +2213,43 @@ TYPED_TEST(TestPlanner, ReturnAsteriskOmitsLambdaSymbols) {
}
}
TYPED_TEST(TestPlanner, ModifyUser) {
{
// Test CREATE USER user WITH PASSWORD 'password'
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstStorage storage;
QUERY(SINGLE_QUERY(CREATE_USER("user", "password")));
CheckPlan<TypeParam>(storage, ExpectModifyUser("user", true));
auto expected =
ExpectDistributed(MakeCheckers(ExpectModifyUser("user", true)));
CheckDistributedPlan<TypeParam>(storage, expected);
}
{
// Test ALTER USER user WITH PASSWORD 'password'
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstStorage storage;
QUERY(SINGLE_QUERY(ALTER_USER("user", "password")));
CheckPlan<TypeParam>(storage, ExpectModifyUser("user", false));
auto expected =
ExpectDistributed(MakeCheckers(ExpectModifyUser("user", false)));
CheckDistributedPlan<TypeParam>(storage, expected);
}
}
TYPED_TEST(TestPlanner, DropUser) {
// Test DROP USER user1, user2, user3
database::SingleNode db;
database::GraphDbAccessor dba(db);
AstStorage storage;
std::vector<std::string> usernames({"user1", "user2", "user3"});
QUERY(SINGLE_QUERY(DROP_USER(usernames)));
CheckPlan<TypeParam>(storage, ExpectDropUser(usernames));
auto expected = ExpectDistributed(MakeCheckers(ExpectDropUser(usernames)));
CheckDistributedPlan<TypeParam>(storage, expected);
}
TYPED_TEST(TestPlanner, DistributedAvg) {
// Test MATCH (n) RETURN AVG(n.prop) AS res
AstStorage storage;