From c4f142e677c64c6419d49d6da1d01a3a6abc7b62 Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Thu, 25 Jul 2019 13:53:44 +0800 Subject: [PATCH 1/5] mysql: make sure the privilege constant variables are consistency --- mysql/const.go | 58 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/mysql/const.go b/mysql/const.go index 334f047eb..c81d8c19b 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -233,6 +233,13 @@ const ( CreateRolePriv // DropRolePriv is the privilege to drop a role. DropRolePriv + + CreateTMPTablePriv + LockTablesPriv + CreateRoutinePriv + AlterRoutinePriv + EventPriv + // AllPriv is the privilege for all actions. AllPriv ) @@ -276,26 +283,31 @@ const PWDHashLen = 40 // Priv2UserCol is the privilege to mysql.user table column name. var Priv2UserCol = map[PrivilegeType]string{ - CreatePriv: "Create_priv", - SelectPriv: "Select_priv", - InsertPriv: "Insert_priv", - UpdatePriv: "Update_priv", - DeletePriv: "Delete_priv", - ShowDBPriv: "Show_db_priv", - SuperPriv: "Super_priv", - CreateUserPriv: "Create_user_priv", - TriggerPriv: "Trigger_priv", - DropPriv: "Drop_priv", - ProcessPriv: "Process_priv", - GrantPriv: "Grant_priv", - ReferencesPriv: "References_priv", - AlterPriv: "Alter_priv", - ExecutePriv: "Execute_priv", - IndexPriv: "Index_priv", - CreateViewPriv: "Create_view_priv", - ShowViewPriv: "Show_view_priv", - CreateRolePriv: "Create_role_priv", - DropRolePriv: "Drop_role_priv", + CreatePriv: "Create_priv", + SelectPriv: "Select_priv", + InsertPriv: "Insert_priv", + UpdatePriv: "Update_priv", + DeletePriv: "Delete_priv", + ShowDBPriv: "Show_db_priv", + SuperPriv: "Super_priv", + CreateUserPriv: "Create_user_priv", + TriggerPriv: "Trigger_priv", + DropPriv: "Drop_priv", + ProcessPriv: "Process_priv", + GrantPriv: "Grant_priv", + ReferencesPriv: "References_priv", + AlterPriv: "Alter_priv", + ExecutePriv: "Execute_priv", + IndexPriv: "Index_priv", + CreateViewPriv: "Create_view_priv", + ShowViewPriv: "Show_view_priv", + CreateRolePriv: "Create_role_priv", + DropRolePriv: "Drop_role_priv", + CreateTMPTablePriv: "Create_tmp_table_priv", + LockTablesPriv: "Lock_tables_priv", + CreateRoutinePriv: "Create_routine_priv", + AlterRoutinePriv: "Alter_routine_priv", + EventPriv: "Event_priv", } // Command2Str is the command information to command name. @@ -358,9 +370,6 @@ var Col2PrivType = map[string]PrivilegeType{ "Drop_role_priv": DropRolePriv, } -// AllGlobalPrivs is all the privileges in global scope. -var AllGlobalPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, ProcessPriv, GrantPriv, ReferencesPriv, AlterPriv, ShowDBPriv, SuperPriv, ExecutePriv, IndexPriv, CreateUserPriv, TriggerPriv, CreateViewPriv, ShowViewPriv, CreateRolePriv, DropRolePriv} - // Priv2Str is the map for privilege to string. var Priv2Str = map[PrivilegeType]string{ CreatePriv: "Create", @@ -419,6 +428,9 @@ var SetStr2Priv = map[string]PrivilegeType{ "Show View": ShowViewPriv, } +// AllGlobalPrivs is all the privileges in global scope. +var AllGlobalPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, ProcessPriv, GrantPriv, ReferencesPriv, AlterPriv, ShowDBPriv, SuperPriv, ExecutePriv, IndexPriv, CreateUserPriv, TriggerPriv, CreateViewPriv, ShowViewPriv, CreateRolePriv, DropRolePriv} + // AllDBPrivs is all the privileges in database scope. var AllDBPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, GrantPriv, AlterPriv, ExecutePriv, IndexPriv, CreateViewPriv, ShowViewPriv} From 5b59348e6ecd6930d53041261e65a85ba73ce59a Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Thu, 25 Jul 2019 15:31:54 +0800 Subject: [PATCH 2/5] make CI run --- mysql/const.go | 4 ++-- mysql/const_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 mysql/const_test.go diff --git a/mysql/const.go b/mysql/const.go index c81d8c19b..175800fc8 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -246,7 +246,7 @@ const ( // AllPrivMask is the mask for PrivilegeType with all bits set to 1. // If it's passed to RequestVerification, it means any privilege would be OK. -const AllPrivMask = AllPriv - 1 +const AllPrivMask = (AllPriv - 1) - 1 // MySQL type maximum length. const ( @@ -429,7 +429,7 @@ var SetStr2Priv = map[string]PrivilegeType{ } // AllGlobalPrivs is all the privileges in global scope. -var AllGlobalPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, ProcessPriv, GrantPriv, ReferencesPriv, AlterPriv, ShowDBPriv, SuperPriv, ExecutePriv, IndexPriv, CreateUserPriv, TriggerPriv, CreateViewPriv, ShowViewPriv, CreateRolePriv, DropRolePriv} +var AllGlobalPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, ProcessPriv, GrantPriv, ReferencesPriv, AlterPriv, ShowDBPriv, SuperPriv, ExecutePriv, IndexPriv, CreateUserPriv, TriggerPriv, CreateViewPriv, ShowViewPriv, CreateRolePriv, DropRolePriv, CreateTMPTablePriv, LockTablesPriv, CreateRoutinePriv, AlterRoutinePriv, EventPriv} // AllDBPrivs is all the privileges in database scope. var AllDBPrivs = []PrivilegeType{SelectPriv, InsertPriv, UpdatePriv, DeletePriv, CreatePriv, DropPriv, GrantPriv, AlterPriv, ExecutePriv, IndexPriv, CreateViewPriv, ShowViewPriv} diff --git a/mysql/const_test.go b/mysql/const_test.go new file mode 100644 index 000000000..1ec0c9a41 --- /dev/null +++ b/mysql/const_test.go @@ -0,0 +1,50 @@ +// Copyright 2017 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mysql + +import ( + "fmt" + "testing" + + . "github.com/pingcap/check" +) + +var _ = Suite(&testConstSuite{}) + +type testConstSuite struct{} + +func TestT(t *testing.T) { + TestingT(t) +} + +func (s *testConstSuite) TestPrivAllConsistency(c *C) { + // AllPriv in mysql.user columns. + for priv := PrivilegeType(CreatePriv); priv != AllPriv; priv = priv << 1 { + _, ok := Priv2UserCol[priv] + c.Assert(ok, IsTrue, Commentf("priv fail %d", priv)) + } + + var allPriv PrivilegeType + for v := range Priv2UserCol { + allPriv = allPriv | v + } + c.Assert(allPriv, Equals, AllPrivMask) + + for _, v := range AllGlobalPrivs { + _, ok := Priv2UserCol[v] + c.Assert(ok, IsTrue) + } + + c.Assert(len(Priv2UserCol), Equals, len(AllGlobalPrivs)) +} From f70091810aebd4e8aebc9d7ff7e2818bad082191 Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Thu, 25 Jul 2019 17:11:10 +0800 Subject: [PATCH 3/5] tiny update --- mysql/const.go | 55 ++++++++++++++++++++++++--------------------- mysql/const_test.go | 15 ++++++++----- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/mysql/const.go b/mysql/const.go index 175800fc8..6addf17ae 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -246,7 +246,7 @@ const ( // AllPrivMask is the mask for PrivilegeType with all bits set to 1. // If it's passed to RequestVerification, it means any privilege would be OK. -const AllPrivMask = (AllPriv - 1) - 1 +const AllPrivMask = AllPriv - 1 // MySQL type maximum length. const ( @@ -310,6 +310,35 @@ var Priv2UserCol = map[PrivilegeType]string{ EventPriv: "Event_priv", } +// Col2PrivType is the privilege tables column name to privilege type. +var Col2PrivType = map[string]PrivilegeType{ + "Create_priv": CreatePriv, + "Select_priv": SelectPriv, + "Insert_priv": InsertPriv, + "Update_priv": UpdatePriv, + "Delete_priv": DeletePriv, + "Show_db_priv": ShowDBPriv, + "Super_priv": SuperPriv, + "Create_user_priv": CreateUserPriv, + "Trigger_priv": TriggerPriv, + "Drop_priv": DropPriv, + "Process_priv": ProcessPriv, + "Grant_priv": GrantPriv, + "References_priv": ReferencesPriv, + "Alter_priv": AlterPriv, + "Execute_priv": ExecutePriv, + "Index_priv": IndexPriv, + "Create_view_priv": CreateViewPriv, + "Show_view_priv": ShowViewPriv, + "Create_role_priv": CreateRolePriv, + "Drop_role_priv": DropRolePriv, + "Create_tmp_table_priv": CreateTMPTablePriv, + "Lock_tables_priv": LockTablesPriv, + "Create_routine_priv": CreateRoutinePriv, + "Alter_routine_priv": AlterRoutinePriv, + "Event_priv": EventPriv, +} + // Command2Str is the command information to command name. var Command2Str = map[byte]string{ ComSleep: "Sleep", @@ -346,30 +375,6 @@ var Command2Str = map[byte]string{ ComResetConnection: "Reset connect", } -// Col2PrivType is the privilege tables column name to privilege type. -var Col2PrivType = map[string]PrivilegeType{ - "Create_priv": CreatePriv, - "Select_priv": SelectPriv, - "Insert_priv": InsertPriv, - "Update_priv": UpdatePriv, - "Delete_priv": DeletePriv, - "Show_db_priv": ShowDBPriv, - "Super_priv": SuperPriv, - "Create_user_priv": CreateUserPriv, - "Trigger_priv": TriggerPriv, - "Drop_priv": DropPriv, - "Process_priv": ProcessPriv, - "Grant_priv": GrantPriv, - "References_priv": ReferencesPriv, - "Alter_priv": AlterPriv, - "Execute_priv": ExecutePriv, - "Index_priv": IndexPriv, - "Create_view_priv": CreateViewPriv, - "Show_view_priv": ShowViewPriv, - "Create_role_priv": CreateRolePriv, - "Drop_role_priv": DropRolePriv, -} - // Priv2Str is the map for privilege to string. var Priv2Str = map[PrivilegeType]string{ CreatePriv: "Create", diff --git a/mysql/const_test.go b/mysql/const_test.go index 1ec0c9a41..5beaeb770 100644 --- a/mysql/const_test.go +++ b/mysql/const_test.go @@ -35,16 +35,19 @@ func (s *testConstSuite) TestPrivAllConsistency(c *C) { c.Assert(ok, IsTrue, Commentf("priv fail %d", priv)) } - var allPriv PrivilegeType - for v := range Priv2UserCol { - allPriv = allPriv | v - } - c.Assert(allPriv, Equals, AllPrivMask) - for _, v := range AllGlobalPrivs { _, ok := Priv2UserCol[v] c.Assert(ok, IsTrue) } c.Assert(len(Priv2UserCol), Equals, len(AllGlobalPrivs)) + + for _, v := range Priv2UserCol { + _, ok := Col2PrivType[v] + c.Assert(ok, IsTrue) + } + for _, v := range Col2PrivType { + _, ok := Priv2UserCol[v] + c.Assert(ok, IsTrue) + } } From 658a2fdc58b3bafd2bc7d30e1769f4cccfaf4d96 Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Thu, 25 Jul 2019 17:57:50 +0800 Subject: [PATCH 4/5] address comment --- mysql/const.go | 45 +++++++++++++++++++++++++-------------------- mysql/const_test.go | 3 ++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/mysql/const.go b/mysql/const.go index 6addf17ae..3a1aad80f 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -377,26 +377,31 @@ var Command2Str = map[byte]string{ // Priv2Str is the map for privilege to string. var Priv2Str = map[PrivilegeType]string{ - CreatePriv: "Create", - SelectPriv: "Select", - InsertPriv: "Insert", - UpdatePriv: "Update", - DeletePriv: "Delete", - ShowDBPriv: "Show Databases", - SuperPriv: "Super", - CreateUserPriv: "Create User", - TriggerPriv: "Trigger", - DropPriv: "Drop", - ProcessPriv: "Process", - GrantPriv: "Grant Option", - ReferencesPriv: "References", - AlterPriv: "Alter", - ExecutePriv: "Execute", - IndexPriv: "Index", - CreateViewPriv: "Create View", - ShowViewPriv: "Show View", - CreateRolePriv: "Create Role", - DropRolePriv: "Drop Role", + CreatePriv: "Create", + SelectPriv: "Select", + InsertPriv: "Insert", + UpdatePriv: "Update", + DeletePriv: "Delete", + ShowDBPriv: "Show Databases", + SuperPriv: "Super", + CreateUserPriv: "Create User", + TriggerPriv: "Trigger", + DropPriv: "Drop", + ProcessPriv: "Process", + GrantPriv: "Grant Option", + ReferencesPriv: "References", + AlterPriv: "Alter", + ExecutePriv: "Execute", + IndexPriv: "Index", + CreateViewPriv: "Create View", + ShowViewPriv: "Show View", + CreateRolePriv: "Create Role", + DropRolePriv: "Drop Role", + CreateTMPTablePriv: "CREATE TEMPORARY TABLES", + LockTablesPriv: "LOCK TABLES", + CreateRoutinePriv: "CREATE ROUTINE", + AlterRoutinePriv: "ALTER ROUTINE", + EventPriv: "EVENT", } // Priv2SetStr is the map for privilege to string. diff --git a/mysql/const_test.go b/mysql/const_test.go index 5beaeb770..763b010a6 100644 --- a/mysql/const_test.go +++ b/mysql/const_test.go @@ -14,7 +14,6 @@ package mysql import ( - "fmt" "testing" . "github.com/pingcap/check" @@ -50,4 +49,6 @@ func (s *testConstSuite) TestPrivAllConsistency(c *C) { _, ok := Priv2UserCol[v] c.Assert(ok, IsTrue) } + + c.Assert(len(Priv2Str), Equals, len(Priv2UserCol)) } From d1ac0d1bf7b814ad5585636ba34e8108aa78a170 Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Thu, 25 Jul 2019 19:20:58 +0800 Subject: [PATCH 5/5] use some privilege --- ast/misc.go | 52 +++++++++----------------------------------------- parser.go | 10 +++++----- parser.y | 10 +++++----- parser_test.go | 2 +- 4 files changed, 20 insertions(+), 54 deletions(-) diff --git a/ast/misc.go b/ast/misc.go index d7f2741ef..3fe50abf1 100755 --- a/ast/misc.go +++ b/ast/misc.go @@ -1612,51 +1612,17 @@ type PrivElem struct { // Restore implements Node interface. func (n *PrivElem) Restore(ctx *RestoreCtx) error { - switch n.Priv { - case 0: + if n.Priv == 0 { ctx.WritePlain("/* UNSUPPORTED TYPE */") - case mysql.AllPriv: + } else if n.Priv == mysql.AllPriv { ctx.WriteKeyWord("ALL") - case mysql.AlterPriv: - ctx.WriteKeyWord("ALTER") - case mysql.CreatePriv: - ctx.WriteKeyWord("CREATE") - case mysql.CreateUserPriv: - ctx.WriteKeyWord("CREATE USER") - case mysql.CreateRolePriv: - ctx.WriteKeyWord("CREATE ROLE") - case mysql.TriggerPriv: - ctx.WriteKeyWord("TRIGGER") - case mysql.DeletePriv: - ctx.WriteKeyWord("DELETE") - case mysql.DropPriv: - ctx.WriteKeyWord("DROP") - case mysql.ProcessPriv: - ctx.WriteKeyWord("PROCESS") - case mysql.ExecutePriv: - ctx.WriteKeyWord("EXECUTE") - case mysql.IndexPriv: - ctx.WriteKeyWord("INDEX") - case mysql.InsertPriv: - ctx.WriteKeyWord("INSERT") - case mysql.SelectPriv: - ctx.WriteKeyWord("SELECT") - case mysql.SuperPriv: - ctx.WriteKeyWord("SUPER") - case mysql.ShowDBPriv: - ctx.WriteKeyWord("SHOW DATABASES") - case mysql.UpdatePriv: - ctx.WriteKeyWord("UPDATE") - case mysql.GrantPriv: - ctx.WriteKeyWord("GRANT OPTION") - case mysql.ReferencesPriv: - ctx.WriteKeyWord("REFERENCES") - case mysql.CreateViewPriv: - ctx.WriteKeyWord("CREATE VIEW") - case mysql.ShowViewPriv: - ctx.WriteKeyWord("SHOW VIEW") - default: - return errors.New("Undefined privilege type") + } else { + str, ok := mysql.Priv2Str[n.Priv] + if ok { + ctx.WriteKeyWord(str) + } else { + return errors.New("Undefined privilege type") + } } if n.Cols != nil { ctx.WritePlain(" (") diff --git a/parser.go b/parser.go index 257c593c4..c4f24ca93 100644 --- a/parser.go +++ b/parser.go @@ -14360,11 +14360,11 @@ yynewstate: } case 1604: { - parser.yyVAL.item = mysql.PrivilegeType(0) + parser.yyVAL.item = mysql.CreateTMPTablePriv } case 1605: { - parser.yyVAL.item = mysql.PrivilegeType(0) + parser.yyVAL.item = mysql.LockTablesPriv } case 1606: { @@ -14384,15 +14384,15 @@ yynewstate: } case 1610: { - parser.yyVAL.item = mysql.PrivilegeType(0) + parser.yyVAL.item = mysql.CreateRoutinePriv } case 1611: { - parser.yyVAL.item = mysql.PrivilegeType(0) + parser.yyVAL.item = mysql.AlterRoutinePriv } case 1612: { - parser.yyVAL.item = mysql.PrivilegeType(0) + parser.yyVAL.item = mysql.EventPriv } case 1613: { diff --git a/parser.y b/parser.y index 995e0be9e..0b208c51a 100644 --- a/parser.y +++ b/parser.y @@ -8658,11 +8658,11 @@ PrivType: } | "CREATE" "TEMPORARY" "TABLES" { - $$ = mysql.PrivilegeType(0) + $$ = mysql.CreateTMPTablePriv } | "LOCK" "TABLES" { - $$ = mysql.PrivilegeType(0) + $$ = mysql.LockTablesPriv } | "CREATE" "VIEW" { @@ -8682,15 +8682,15 @@ PrivType: } | "CREATE" "ROUTINE" { - $$ = mysql.PrivilegeType(0) + $$ = mysql.CreateRoutinePriv } | "ALTER" "ROUTINE" { - $$ = mysql.PrivilegeType(0) + $$ = mysql.AlterRoutinePriv } | "EVENT" { - $$ = mysql.PrivilegeType(0) + $$ = mysql.EventPriv } ObjectType: diff --git a/parser_test.go b/parser_test.go index 3b06927dd..463ccc338 100755 --- a/parser_test.go +++ b/parser_test.go @@ -2393,7 +2393,7 @@ func (s *testParserSuite) TestPrivilege(c *C) { {"grant all privileges on zabbix.* to 'zabbix'@'localhost' identified by 'password';", true, "GRANT ALL ON `zabbix`.* TO `zabbix`@`localhost` IDENTIFIED BY 'password'"}, {"GRANT SELECT ON test.* to 'test'", true, "GRANT SELECT ON `test`.* TO `test`@`%`"}, // For issue 2654. {"grant PROCESS,usage, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'xxxxxxxxxx'@'%' identified by password 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'", true, "GRANT PROCESS /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */ ON *.* TO `xxxxxxxxxx`@`%` IDENTIFIED BY PASSWORD 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'"}, // For issue 4865 - {"/* rds internal mark */ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, RELOAD, PROCESS, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER on *.* to 'root2'@'%' identified by password '*sdsadsdsadssadsadsadsadsada' with grant option", true, "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES /* UNSUPPORTED TYPE */, PROCESS, INDEX, ALTER /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */, EXECUTE /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */, CREATE VIEW, SHOW VIEW /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */, CREATE USER /* UNSUPPORTED TYPE */, TRIGGER ON *.* TO `root2`@`%` IDENTIFIED BY PASSWORD '*sdsadsdsadssadsadsadsadsada' WITH GRANT OPTION"}, + {"/* rds internal mark */ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, RELOAD, PROCESS, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER on *.* to 'root2'@'%' identified by password '*sdsadsdsadssadsadsadsadsada' with grant option", true, "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES /* UNSUPPORTED TYPE */, PROCESS, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE /* UNSUPPORTED TYPE */ /* UNSUPPORTED TYPE */, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER ON *.* TO `root2`@`%` IDENTIFIED BY PASSWORD '*sdsadsdsadssadsadsadsadsada' WITH GRANT OPTION"}, {"GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost';", true, "GRANT `role1`@`%`, `role2`@`%` TO `user1`@`localhost`, `user2`@`localhost`"}, {"GRANT 'u1' TO 'u1';", true, "GRANT `u1`@`%` TO `u1`@`%`"}, {"GRANT 'app_developer' TO 'dev1'@'localhost';", true, "GRANT `app_developer`@`%` TO `dev1`@`localhost`"},