diff --git a/src/distributed/plan_rpc_messages.lcp b/src/distributed/plan_rpc_messages.lcp index c0e16dc8d..bf2f892a5 100644 --- a/src/distributed/plan_rpc_messages.lcp +++ b/src/distributed/plan_rpc_messages.lcp @@ -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::kHelperId));" + :load-fun "storage = std::move(ar.template get_helper(query::AstStorage::kHelperId));" :capnp-save :dont-save))) (:response ())) diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp index 2b04e78f5..e9352ee63 100644 --- a/src/query/exceptions.hpp +++ b/src/query/exceptions.hpp @@ -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 diff --git a/src/query/frontend/ast/ast.capnp b/src/query/frontend/ast/ast.capnp index 4269cc6c7..17bc814d5 100644 --- a/src/query/frontend/ast/ast.capnp +++ b/src/query/frontend/ast/ast.capnp @@ -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); +} diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index d0cc1d4fd..417ac7587 100644 --- a/src/query/frontend/ast/ast.cpp +++ b/src/query/frontend/ast/ast.cpp @@ -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(); } +// ModifyUser. +void ModifyUser::Save(capnp::Clause::Builder *clause_builder, + std::vector *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 *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 *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(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(); +} + +// DropUser. +void DropUser::Save(capnp::Clause::Builder *clause_builder, + std::vector *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 *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 *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(); +} + // CypherUnion void CypherUnion::Save(capnp::Tree::Builder *tree_builder, std::vector *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); diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index 11444274b..989681d5e 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -3499,6 +3499,100 @@ class CreateIndex : public Clause { const unsigned int); }; +class ModifyUser : public Clause { + friend class AstStorage; + + public: + DEFVISITABLE(TreeVisitor); + DEFVISITABLE(HierarchicalTreeVisitor); + + ModifyUser *Clone(AstStorage &storage) const override { + return storage.Create( + 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 *saved_uids) override; + virtual void Save(capnp::ModifyUser::Builder *builder, + std::vector *saved_uids); + void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage, + std::vector *loaded_uids) override; + + private: + friend class boost::serialization::access; + + template + void serialize(TArchive &ar, const unsigned int) { + ar &boost::serialization::base_object(*this); + ar &username_ &password_ &is_create_; + } + + template + friend void boost::serialization::load_construct_data(TArchive &, + ModifyUser *, + const unsigned int); +}; + +class DropUser : public Clause { + friend class AstStorage; + + public: + DEFVISITABLE(TreeVisitor); + DEFVISITABLE(HierarchicalTreeVisitor); + + DropUser *Clone(AstStorage &storage) const override { + return storage.Create(usernames_); + } + + static DropUser *Construct(const capnp::DropUser::Reader &reader, + AstStorage *storage); + using Clause::Save; + + std::vector usernames_; + + protected: + explicit DropUser(int uid) : Clause(uid) {} + DropUser(int uid, std::vector usernames) + : Clause(uid), usernames_(usernames) {} + + void Save(capnp::Clause::Builder *builder, + std::vector *saved_uids) override; + virtual void Save(capnp::DropUser::Builder *builder, + std::vector *saved_uids); + void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage, + std::vector *loaded_uids) override; + + private: + friend class boost::serialization::access; + + template + void serialize(TArchive &ar, const unsigned int) { + ar &boost::serialization::base_object(*this); + ar &usernames_; + } + + template + 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); diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index e0de05911..ff3290e0f 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -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; +using TreeLeafVisitor = + ::utils::LeafVisitor; 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 diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 24042794a..85647a65c 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -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(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(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(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( ctx->createIndex()->accept(this).as()); } + if (ctx->modifyUser()) { + return static_cast( + ctx->modifyUser()->accept(this).as()); + } + if (ctx->dropUser()) { + return static_cast( + ctx->dropUser()->accept(this).as()); + } // 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(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(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 usernames; + for (auto username_ptr : ctx->userName()) + usernames.emplace_back(username_ptr->getText()); + return storage_.Create(usernames); +} + antlrcpp::Any CypherMainVisitor::visitCypherReturn( CypherParser::CypherReturnContext *ctx) { auto *return_clause = storage_.Create(); @@ -949,10 +1011,11 @@ antlrcpp::Any CypherMainVisitor::visitLiteral( return static_cast( storage_.Create(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( storage_.Create(token_position)); } else if (ctx->StringLiteral()) { diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp index 01f1b3bcd..49e0bb262 100644 --- a/src/query/frontend/ast/cypher_main_visitor.hpp +++ b/src/query/frontend/ast/cypher_main_visitor.hpp @@ -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* */ diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4 index 7d0a35137..25ad537a2 100644 --- a/src/query/frontend/opencypher/grammar/Cypher.g4 +++ b/src/query/frontend/opencypher/grammar/Cypher.g4 @@ -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] ; - diff --git a/src/query/frontend/semantic/symbol_generator.cpp b/src/query/frontend/semantic/symbol_generator.cpp index e1d5392df..27d8bb24c 100644 --- a/src/query/frontend/semantic/symbol_generator.cpp +++ b/src/query/frontend/semantic/symbol_generator.cpp @@ -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) { diff --git a/src/query/frontend/semantic/symbol_generator.hpp b/src/query/frontend/semantic/symbol_generator.hpp index 178fbafb0..1d63ae22c 100644 --- a/src/query/frontend/semantic/symbol_generator.hpp +++ b/src/query/frontend/semantic/symbol_generator.hpp @@ -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; diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp index 153d47b13..ab3b930d4 100644 --- a/src/query/interpret/eval.hpp +++ b/src/query/interpret/eval.hpp @@ -56,6 +56,8 @@ class ExpressionEvaluator : public TreeVisitor { BLOCK_VISIT(Merge); BLOCK_VISIT(Unwind); BLOCK_VISIT(CreateIndex); + BLOCK_VISIT(ModifyUser); + BLOCK_VISIT(DropUser); #undef BLOCK_VISIT diff --git a/src/query/plan/cost_estimator.hpp b/src/query/plan/cost_estimator.hpp index 9fab0cb55..a8f9c0792 100644 --- a/src/query/plan/cost_estimator.hpp +++ b/src/query/plan/cost_estimator.hpp @@ -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 ¶meters) : 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? diff --git a/src/query/plan/distributed.cpp b/src/query/plan/distributed.cpp index 09964c750..41a3f919c 100644 --- a/src/query/plan/distributed.cpp +++ b/src/query/plan/distributed.cpp @@ -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 diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 13f1cd87e..bbbf7238d 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -413,32 +413,32 @@ std::unique_ptr ScanAllByLabelPropertyRange::MakeCursor( -> std::experimental::optional { - ExpressionEvaluator evaluator(frame, context.parameters_, - context.symbol_table_, db, graph_view_); - auto convert = [&evaluator](const auto &bound) - -> std::experimental::optional> { - if (!bound) return std::experimental::nullopt; - auto value = bound->value()->Accept(evaluator); - try { - return std::experimental::make_optional( - utils::Bound(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> { + 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(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>( output_symbol_, input_->MakeCursor(db), std::move(vertices), db); } @@ -461,18 +461,18 @@ std::unique_ptr ScanAllByLabelPropertyValue::MakeCursor( auto vertices = [this, &db](Frame &frame, Context &context) -> std::experimental::optional { - 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>( 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 PullRemoteOrderBy::MakeCursor( return std::make_unique(*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 ModifyUser::MakeCursor( + database::GraphDbAccessor &db) const { + return std::make_unique(*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 DropUser::MakeCursor( + database::GraphDbAccessor &db) const { + return std::make_unique(*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); diff --git a/src/query/plan/operator.lcp b/src/query/plan/operator.lcp index dfb92cb17..8f85feba2 100644 --- a/src/query/plan/operator.lcp +++ b/src/query/plan/operator.lcp @@ -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; +using LogicalOperatorLeafVisitor = + ::utils::LeafVisitor; /** * @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" + :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 MakeCursor( + database::GraphDbAccessor &db) const override; + + std::vector ModifiedSymbols(const SymbolTable &) const override { + return std::vector(); + } + + bool HasSingleInput() const override; + std::shared_ptr input() const override; + void set_input(std::shared_ptr 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" + :capnp-save #'save-operator-pointer + :capnp-load #'load-operator-pointer) + (usernames "std::vector" :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 usernames): usernames_(usernames) {} + + bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor( + database::GraphDbAccessor &db) const override; + + std::vector ModifiedSymbols(const SymbolTable &) const override { + return std::vector(); + } + + bool HasSingleInput() const override; + std::shared_ptr input() const override; + void set_input(std::shared_ptr 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<# diff --git a/src/query/plan/preprocess.hpp b/src/query/plan/preprocess.hpp index dbbb94c6b..c1930be77 100644 --- a/src/query/plan/preprocess.hpp +++ b/src/query/plan/preprocess.hpp @@ -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 symbols_; const SymbolTable &symbol_table_; diff --git a/src/query/plan/rule_based_planner.cpp b/src/query/plan/rule_based_planner.cpp index 929daac48..a4393b13e 100644 --- a/src/query/plan/rule_based_planner.cpp +++ b/src/query/plan/rule_based_planner.cpp @@ -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. diff --git a/src/query/plan/rule_based_planner.hpp b/src/query/plan/rule_based_planner.hpp index f6054dcdf..0acf815cf 100644 --- a/src/query/plan/rule_based_planner.hpp +++ b/src/query/plan/rule_based_planner.hpp @@ -182,6 +182,15 @@ class RuleBasedPlanner { DCHECK(!input_op) << "Unexpected operator before CreateIndex"; input_op = std::make_unique( create_index->label_, create_index->property_); + } else if (auto *modify_user = + dynamic_cast(clause)) { + DCHECK(!input_op) << "Unexpected operator before ModifyUser"; + input_op = std::make_unique( + modify_user->username_, modify_user->password_, + modify_user->is_create_); + } else if (auto *drop_user = dynamic_cast(clause)) { + DCHECK(!input_op) << "Unexpected operator before DropUser"; + input_op = std::make_unique(drop_user->usernames_); } else { throw utils::NotYetImplemented("clause conversion to operator(s)"); } diff --git a/src/utils/serialization.hpp b/src/utils/serialization.hpp index aead65a8b..48a9cc8dd 100644 --- a/src/utils/serialization.hpp +++ b/src/utils/serialization.hpp @@ -185,6 +185,13 @@ inline void SaveVector(const std::vector &data, } } +inline void SaveVector(const std::vector &data, + ::capnp::List<::capnp::Text>::Builder *list_builder) { + for (size_t i = 0; i < data.size(); ++i) { + list_builder->set(i, data[i]); + } +} + template inline void LoadVector(std::vector *data, const typename ::capnp::List::Reader &list_reader) { @@ -193,6 +200,13 @@ inline void LoadVector(std::vector *data, } } +inline void LoadVector( + std::vector *data, + const typename ::capnp::List<::capnp::Text>::Reader &list_reader) { + for (const auto e : list_reader) { + data->emplace_back(e); } +} + template inline void SaveVector( const std::vector &data, diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp index 1a04ff67c..eb90a85ec 100644 --- a/tests/manual/query_planner.cpp +++ b/tests/manual/query_planner.cpp @@ -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() << "] {"; diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 17791736a..86c699654 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -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 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(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 &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(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 diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 391ccb3fc..5b0d10aec 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -607,3 +607,8 @@ auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match, storage.Create( \ storage.Create(accumulator), initializer, \ storage.Create(variable), list, expr) +#define CREATE_USER(username, password) \ + storage.Create((username), LITERAL(password), true) +#define ALTER_USER(username, password) \ + storage.Create((username), LITERAL(password), false) +#define DROP_USER(usernames) storage.Create((usernames)) diff --git a/tests/unit/query_planner.cpp b/tests/unit/query_planner.cpp index bea2d32b7..920dd91b0 100644 --- a/tests/unit/query_planner.cpp +++ b/tests/unit/query_planner.cpp @@ -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 checkers_; @@ -484,6 +493,37 @@ class Planner { std::unique_ptr plan_; }; +class ExpectModifyUser : public OpChecker { + 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(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 { + public: + ExpectDropUser(std::vector usernames) : usernames_(usernames) {} + + void ExpectOp(DropUser &drop_user, const SymbolTable &) override { + EXPECT_EQ(usernames_, drop_user.usernames()); + } + + private: + std::vector usernames_; +}; + class SerializedPlanner { public: SerializedPlanner(std::vector 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(storage, ExpectModifyUser("user", true)); + auto expected = + ExpectDistributed(MakeCheckers(ExpectModifyUser("user", true))); + CheckDistributedPlan(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(storage, ExpectModifyUser("user", false)); + auto expected = + ExpectDistributed(MakeCheckers(ExpectModifyUser("user", false))); + CheckDistributedPlan(storage, expected); + } +} + +TYPED_TEST(TestPlanner, DropUser) { + // Test DROP USER user1, user2, user3 + database::SingleNode db; + database::GraphDbAccessor dba(db); + AstStorage storage; + std::vector usernames({"user1", "user2", "user3"}); + QUERY(SINGLE_QUERY(DROP_USER(usernames))); + CheckPlan(storage, ExpectDropUser(usernames)); + auto expected = ExpectDistributed(MakeCheckers(ExpectDropUser(usernames))); + CheckDistributedPlan(storage, expected); +} + TYPED_TEST(TestPlanner, DistributedAvg) { // Test MATCH (n) RETURN AVG(n.prop) AS res AstStorage storage;