diff --git a/executor/simple.go b/executor/simple.go index f03edcd947..bafd310d5f 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -646,9 +646,12 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm return err1 } if exists { + user := fmt.Sprintf(`'%s'@'%s'`, spec.User.Username, spec.User.Hostname) if !s.IfNotExists { - return errors.New("Duplicate user") + return ErrCannotUser.GenWithStackByArgs("CREATE USER", user) } + err := infoschema.ErrUserAlreadyExists.GenWithStackByArgs(user) + e.ctx.GetSessionVars().StmtCtx.AppendNote(err) continue } pwd, ok := spec.EncodedPassword() @@ -697,10 +700,8 @@ func (e *SimpleExec) executeAlterUser(s *ast.AlterUserStmt) error { return err } if !exists { - failedUsers = append(failedUsers, spec.User.String()) - // TODO: Make this error as a warning. - // if s.IfExists { - // } + user := fmt.Sprintf(`'%s'@'%s'`, spec.User.Username, spec.User.Hostname) + failedUsers = append(failedUsers, user) continue } pwd := "" @@ -728,7 +729,13 @@ func (e *SimpleExec) executeAlterUser(s *ast.AlterUserStmt) error { if err != nil { return err } - return ErrCannotUser.GenWithStackByArgs("ALTER USER", strings.Join(failedUsers, ",")) + if !s.IfExists { + return ErrCannotUser.GenWithStackByArgs("ALTER USER", strings.Join(failedUsers, ",")) + } + for _, user := range failedUsers { + err := infoschema.ErrUserDropExists.GenWithStackByArgs(user) + e.ctx.GetSessionVars().StmtCtx.AppendNote(err) + } } domain.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx) return nil diff --git a/executor/simple_test.go b/executor/simple_test.go index e704d250e0..f237c494b6 100644 --- a/executor/simple_test.go +++ b/executor/simple_test.go @@ -17,6 +17,7 @@ import ( "context" . "github.com/pingcap/check" + "github.com/pingcap/errors" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" @@ -254,8 +255,10 @@ func (s *testSuite3) TestUser(c *C) { // Create duplicate user without IfNotExists will cause error. createUserSQL = `CREATE USER 'test'@'localhost' IDENTIFIED BY '123';` - _, err := tk.Exec(createUserSQL) - c.Check(err, NotNil) + tk.MustGetErrCode(createUserSQL, mysql.ErrCannotUser) + createUserSQL = `CREATE USER IF NOT EXISTS 'test'@'localhost' IDENTIFIED BY '123';` + tk.MustExec(createUserSQL) + tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Note|3163|User 'test'@'localhost' already exists.")) dropUserSQL := `DROP USER IF EXISTS 'test'@'localhost' ;` tk.MustExec(dropUserSQL) // Create user test. @@ -274,20 +277,28 @@ func (s *testSuite3) TestUser(c *C) { tk.MustExec(alterUserSQL) result = tk.MustQuery(`SELECT Password FROM mysql.User WHERE User="test1" and Host="localhost"`) result.Check(testkit.Rows(auth.EncodePassword("111"))) + alterUserSQL = `ALTER USER 'test_not_exist'@'localhost' IDENTIFIED BY '111';` + tk.MustGetErrCode(alterUserSQL, mysql.ErrCannotUser) + alterUserSQL = `ALTER USER 'test1'@'localhost' IDENTIFIED BY '222', 'test_not_exist'@'localhost' IDENTIFIED BY '111';` + tk.MustGetErrCode(alterUserSQL, mysql.ErrCannotUser) + result = tk.MustQuery(`SELECT Password FROM mysql.User WHERE User="test1" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("222"))) + alterUserSQL = `ALTER USER IF EXISTS 'test2'@'localhost' IDENTIFIED BY '222', 'test_not_exist'@'localhost' IDENTIFIED BY '1';` - _, err = tk.Exec(alterUserSQL) - c.Check(err, NotNil) + tk.MustExec(alterUserSQL) + tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Note|3162|User 'test_not_exist'@'localhost' does not exist.")) result = tk.MustQuery(`SELECT Password FROM mysql.User WHERE User="test2" and Host="localhost"`) result.Check(testkit.Rows(auth.EncodePassword("222"))) alterUserSQL = `ALTER USER IF EXISTS'test_not_exist'@'localhost' IDENTIFIED BY '1', 'test3'@'localhost' IDENTIFIED BY '333';` - _, err = tk.Exec(alterUserSQL) - c.Check(err, NotNil) + tk.MustExec(alterUserSQL) + tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Note|3162|User 'test_not_exist'@'localhost' does not exist.")) result = tk.MustQuery(`SELECT Password FROM mysql.User WHERE User="test3" and Host="localhost"`) result.Check(testkit.Rows(auth.EncodePassword("333"))) + // Test alter user user(). alterUserSQL = `ALTER USER USER() IDENTIFIED BY '1';` - _, err = tk.Exec(alterUserSQL) - c.Check(err, NotNil) + _, err := tk.Exec(alterUserSQL) + c.Check(terror.ErrorEqual(err, errors.New("Session user is empty")), IsTrue, Commentf("err %v", err)) tk.Se, err = session.CreateSession4Test(s.store) c.Check(err, IsNil) ctx := tk.Se.(sessionctx.Context) @@ -309,14 +320,11 @@ func (s *testSuite3) TestUser(c *C) { createUserSQL = `CREATE USER 'test1'@'localhost', 'test3'@'localhost';` tk.MustExec(createUserSQL) dropUserSQL = `DROP USER 'test1'@'localhost', 'test2'@'localhost', 'test3'@'localhost';` - _, err = tk.Exec(dropUserSQL) - c.Check(err, NotNil) + tk.MustGetErrCode(dropUserSQL, mysql.ErrCannotUser) dropUserSQL = `DROP USER 'test3'@'localhost';` - _, err = tk.Exec(dropUserSQL) - c.Check(err, NotNil) + tk.MustGetErrCode(dropUserSQL, mysql.ErrCannotUser) dropUserSQL = `DROP USER 'test1'@'localhost';` - _, err = tk.Exec(dropUserSQL) - c.Check(err, NotNil) + tk.MustGetErrCode(dropUserSQL, mysql.ErrCannotUser) // Test positive cases without IF EXISTS. createUserSQL = `CREATE USER 'test1'@'localhost', 'test3'@'localhost';` tk.MustExec(createUserSQL) diff --git a/go.mod b/go.mod index 3a536ee7e3..ba8906ebb9 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e github.com/pingcap/kvproto v0.0.0-20191118050206-47672e7eabc0 github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 - github.com/pingcap/parser v0.0.0-20191120083129-cbaf2bd7f17d + github.com/pingcap/parser v0.0.0-20191120165920-d5c49d11cc64 github.com/pingcap/pd v1.1.0-beta.0.20190923032047-5c648dc365e0 github.com/pingcap/tidb-tools v3.0.6-0.20191106033616-90632dda3863+incompatible github.com/pingcap/tipb v0.0.0-20191120020146-6161b015e21e diff --git a/go.sum b/go.sum index c486d2a74d..ab529c6fc6 100644 --- a/go.sum +++ b/go.sum @@ -183,8 +183,8 @@ github.com/pingcap/kvproto v0.0.0-20191118050206-47672e7eabc0/go.mod h1:WWLmULLO github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd/go.mod h1:WpHUKhNZ18v116SvGrmjkA9CBhYmuUTKL+p8JC9ANEw= github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 h1:AJD9pZYm72vMgPcQDww9rkZ1DnWfl0pXV3BOWlkYIjA= github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= -github.com/pingcap/parser v0.0.0-20191120083129-cbaf2bd7f17d h1:+ev1tNLx9uiVay2b4s9U+vKDWs4z2CCpD41RGJuOZjY= -github.com/pingcap/parser v0.0.0-20191120083129-cbaf2bd7f17d/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= +github.com/pingcap/parser v0.0.0-20191120165920-d5c49d11cc64 h1:jpLGhi9hEp6Px9NDKkSjpcWuBdkgSCTxGMlrw9bWipQ= +github.com/pingcap/parser v0.0.0-20191120165920-d5c49d11cc64/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= github.com/pingcap/pd v1.1.0-beta.0.20190923032047-5c648dc365e0 h1:GIEq+wZfrl2bcJxpuSrEH4H7/nlf5YdmpS+dU9lNIt8= github.com/pingcap/pd v1.1.0-beta.0.20190923032047-5c648dc365e0/go.mod h1:G/6rJpnYwM0LKMec2rI82/5Kg6GaZMvlfB+e6/tvYmI= github.com/pingcap/tidb-tools v3.0.6-0.20191106033616-90632dda3863+incompatible h1:H1jg0aDWz2SLRh3hNBo2HFtnuHtudIUvBumU7syRkic= diff --git a/infoschema/infoschema.go b/infoschema/infoschema.go index 22393d7acd..c51c150711 100644 --- a/infoschema/infoschema.go +++ b/infoschema/infoschema.go @@ -27,50 +27,52 @@ import ( ) var ( - // ErrDatabaseDropExists returns for dropping a non-existent database. - ErrDatabaseDropExists = terror.ClassSchema.New(codeDBDropExists, "Can't drop database '%s'; database doesn't exist") - // ErrDatabaseNotExists returns for database not exists. - ErrDatabaseNotExists = terror.ClassSchema.New(codeDatabaseNotExists, "Unknown database '%s'") - // ErrTableNotExists returns for table not exists. - ErrTableNotExists = terror.ClassSchema.New(codeTableNotExists, "Table '%s.%s' doesn't exist") - // ErrColumnNotExists returns for column not exists. - ErrColumnNotExists = terror.ClassSchema.New(codeColumnNotExists, "Unknown column '%s' in '%s'") - // ErrForeignKeyNotMatch returns for foreign key not match. - ErrForeignKeyNotMatch = terror.ClassSchema.New(codeWrongFkDef, "Incorrect foreign key definition for '%s': Key reference and table reference don't match") - // ErrCannotAddForeign returns for foreign key exists. - ErrCannotAddForeign = terror.ClassSchema.New(codeCannotAddForeign, "Cannot add foreign key constraint") - // ErrForeignKeyNotExists returns for foreign key not exists. - ErrForeignKeyNotExists = terror.ClassSchema.New(codeForeignKeyNotExists, "Can't DROP '%s'; check that column/key exists") // ErrDatabaseExists returns for database already exists. - ErrDatabaseExists = terror.ClassSchema.New(codeDatabaseExists, "Can't create database '%s'; database exists") - // ErrTableExists returns for table already exists. - ErrTableExists = terror.ClassSchema.New(codeTableExists, "Table '%s' already exists") - // ErrTableDropExists returns for dropping a non-existent table. - ErrTableDropExists = terror.ClassSchema.New(codeBadTable, "Unknown table '%s'") - // ErrUserDropExists returns for dropping a non-existent user. - ErrUserDropExists = terror.ClassSchema.New(codeBadUser, "User %s does not exist.") - // ErrColumnExists returns for column already exists. - ErrColumnExists = terror.ClassSchema.New(codeColumnExists, "Duplicate column name '%s'") - // ErrIndexExists returns for index already exists. - ErrIndexExists = terror.ClassSchema.New(codeIndexExists, "Duplicate Index") - // ErrKeyNameDuplicate returns for index duplicate when rename index. - ErrKeyNameDuplicate = terror.ClassSchema.New(codeKeyNameDuplicate, "Duplicate key name '%s'") - // ErrKeyNotExists returns for index not exists. - ErrKeyNotExists = terror.ClassSchema.New(codeKeyNotExists, "Key '%s' doesn't exist in table '%s'") - // ErrMultiplePriKey returns for multiple primary keys. - ErrMultiplePriKey = terror.ClassSchema.New(codeMultiplePriKey, "Multiple primary key defined") - // ErrTooManyKeyParts returns for too many key parts. - ErrTooManyKeyParts = terror.ClassSchema.New(codeTooManyKeyParts, "Too many key parts specified; max %d parts allowed") - // ErrTableNotLockedForWrite returns for write tables when only hold the table read lock. - ErrTableNotLockedForWrite = terror.ClassSchema.New(codeErrTableNotLockedForWrite, mysql.MySQLErrName[mysql.ErrTableNotLockedForWrite]) - // ErrTableNotLocked returns when session has explicitly lock tables, then visit unlocked table will return this error. - ErrTableNotLocked = terror.ClassSchema.New(codeErrTableNotLocked, mysql.MySQLErrName[mysql.ErrTableNotLocked]) - // ErrNonuniqTable returns when none unique tables errors. - ErrNonuniqTable = terror.ClassSchema.New(codeErrTableNotLocked, mysql.MySQLErrName[mysql.ErrNonuniqTable]) - // ErrTableLocked returns when the table was locked by other session. - ErrTableLocked = terror.ClassSchema.New(codeTableLocked, mysql.MySQLErrName[mysql.ErrTableLocked]) + ErrDatabaseExists = terror.ClassSchema.New(mysql.ErrDBCreateExists, mysql.MySQLErrName[mysql.ErrDBCreateExists]) + // ErrDatabaseDropExists returns for dropping a non-existent database. + ErrDatabaseDropExists = terror.ClassSchema.New(mysql.ErrDBDropExists, mysql.MySQLErrName[mysql.ErrDBDropExists]) // ErrAccessDenied return when the user doesn't have the permission to access the table. - ErrAccessDenied = terror.ClassSchema.New(codeErrAccessDenied, mysql.MySQLErrName[mysql.ErrAccessDenied]) + ErrAccessDenied = terror.ClassSchema.New(mysql.ErrAccessDenied, mysql.MySQLErrName[mysql.ErrAccessDenied]) + // ErrDatabaseNotExists returns for database not exists. + ErrDatabaseNotExists = terror.ClassSchema.New(mysql.ErrBadDB, mysql.MySQLErrName[mysql.ErrBadDB]) + // ErrTableExists returns for table already exists. + ErrTableExists = terror.ClassSchema.New(mysql.ErrTableExists, mysql.MySQLErrName[mysql.ErrTableExists]) + // ErrTableDropExists returns for dropping a non-existent table. + ErrTableDropExists = terror.ClassSchema.New(mysql.ErrBadTable, mysql.MySQLErrName[mysql.ErrBadTable]) + // ErrColumnNotExists returns for column not exists. + ErrColumnNotExists = terror.ClassSchema.New(mysql.ErrBadField, mysql.MySQLErrName[mysql.ErrBadField]) + // ErrColumnExists returns for column already exists. + ErrColumnExists = terror.ClassSchema.New(mysql.ErrDupFieldName, mysql.MySQLErrName[mysql.ErrDupFieldName]) + // ErrKeyNameDuplicate returns for index duplicate when rename index. + ErrKeyNameDuplicate = terror.ClassSchema.New(mysql.ErrDupKeyName, mysql.MySQLErrName[mysql.ErrDupKeyName]) + // ErrNonuniqTable returns when none unique tables errors. + ErrNonuniqTable = terror.ClassSchema.New(mysql.ErrNonuniqTable, mysql.MySQLErrName[mysql.ErrNonuniqTable]) + // ErrMultiplePriKey returns for multiple primary keys. + ErrMultiplePriKey = terror.ClassSchema.New(mysql.ErrMultiplePriKey, mysql.MySQLErrName[mysql.ErrMultiplePriKey]) + // ErrTooManyKeyParts returns for too many key parts. + ErrTooManyKeyParts = terror.ClassSchema.New(mysql.ErrTooManyKeyParts, mysql.MySQLErrName[mysql.ErrTooManyKeyParts]) + // ErrForeignKeyNotExists returns for foreign key not exists. + ErrForeignKeyNotExists = terror.ClassSchema.New(mysql.ErrCantDropFieldOrKey, mysql.MySQLErrName[mysql.ErrCantDropFieldOrKey]) + // ErrTableNotLockedForWrite returns for write tables when only hold the table read lock. + ErrTableNotLockedForWrite = terror.ClassSchema.New(mysql.ErrTableNotLockedForWrite, mysql.MySQLErrName[mysql.ErrTableNotLockedForWrite]) + // ErrTableNotLocked returns when session has explicitly lock tables, then visit unlocked table will return this error. + ErrTableNotLocked = terror.ClassSchema.New(mysql.ErrTableNotLocked, mysql.MySQLErrName[mysql.ErrTableNotLocked]) + // ErrTableNotExists returns for table not exists. + ErrTableNotExists = terror.ClassSchema.New(mysql.ErrNoSuchTable, mysql.MySQLErrName[mysql.ErrNoSuchTable]) + // ErrKeyNotExists returns for index not exists. + ErrKeyNotExists = terror.ClassSchema.New(mysql.ErrKeyDoesNotExist, mysql.MySQLErrName[mysql.ErrKeyDoesNotExist]) + // ErrCannotAddForeign returns for foreign key exists. + ErrCannotAddForeign = terror.ClassSchema.New(mysql.ErrCannotAddForeign, mysql.MySQLErrName[mysql.ErrCannotAddForeign]) + // ErrForeignKeyNotMatch returns for foreign key not match. + ErrForeignKeyNotMatch = terror.ClassSchema.New(mysql.ErrWrongFkDef, mysql.MySQLErrName[mysql.ErrWrongFkDef]) + // ErrIndexExists returns for index already exists. + ErrIndexExists = terror.ClassSchema.New(mysql.ErrDupIndex, mysql.MySQLErrName[mysql.ErrDupIndex]) + // ErrUserDropExists returns for dropping a non-existent user. + ErrUserDropExists = terror.ClassSchema.New(mysql.ErrBadUser, mysql.MySQLErrName[mysql.ErrBadUser]) + // ErrUserAlreadyExists return for creating a existent user. + ErrUserAlreadyExists = terror.ClassSchema.New(mysql.ErrUserAlreadyExists, mysql.MySQLErrName[mysql.ErrUserAlreadyExists]) + // ErrTableLocked returns when the table was locked by other session. + ErrTableLocked = terror.ClassSchema.New(mysql.ErrTableLocked, mysql.MySQLErrName[mysql.ErrTableLocked]) ) // InfoSchema is the interface used to retrieve the schema information. @@ -317,59 +319,31 @@ func (h *Handle) EmptyClone() *Handle { return newHandle } -// Schema error codes. -const ( - codeDBDropExists terror.ErrCode = 1008 - codeDatabaseNotExists = 1049 - codeTableNotExists = 1146 - codeColumnNotExists = 1054 - - codeCannotAddForeign = 1215 - codeForeignKeyNotExists = 1091 - codeWrongFkDef = 1239 - - codeDatabaseExists = 1007 - codeTableExists = 1050 - codeBadTable = 1051 - codeBadUser = 3162 - codeColumnExists = 1060 - codeIndexExists = 1831 - codeMultiplePriKey = 1068 - codeTooManyKeyParts = 1070 - codeKeyNameDuplicate = 1061 - codeKeyNotExists = 1176 - - codeErrTableNotLockedForWrite = mysql.ErrTableNotLockedForWrite - codeErrTableNotLocked = mysql.ErrTableNotLocked - codeErrNonuniqTable = mysql.ErrNonuniqTable - codeErrAccessDenied = mysql.ErrAccessDenied - codeTableLocked = mysql.ErrTableLocked -) - func init() { schemaMySQLErrCodes := map[terror.ErrCode]uint16{ - codeDBDropExists: mysql.ErrDBDropExists, - codeDatabaseNotExists: mysql.ErrBadDB, - codeTableNotExists: mysql.ErrNoSuchTable, - codeColumnNotExists: mysql.ErrBadField, - codeCannotAddForeign: mysql.ErrCannotAddForeign, - codeWrongFkDef: mysql.ErrWrongFkDef, - codeForeignKeyNotExists: mysql.ErrCantDropFieldOrKey, - codeDatabaseExists: mysql.ErrDBCreateExists, - codeTableExists: mysql.ErrTableExists, - codeBadTable: mysql.ErrBadTable, - codeBadUser: mysql.ErrBadUser, - codeColumnExists: mysql.ErrDupFieldName, - codeIndexExists: mysql.ErrDupIndex, - codeMultiplePriKey: mysql.ErrMultiplePriKey, - codeTooManyKeyParts: mysql.ErrTooManyKeyParts, - codeKeyNameDuplicate: mysql.ErrDupKeyName, - codeKeyNotExists: mysql.ErrKeyDoesNotExist, - codeErrTableNotLockedForWrite: mysql.ErrTableNotLockedForWrite, - codeErrTableNotLocked: mysql.ErrTableNotLocked, - codeErrNonuniqTable: mysql.ErrNonuniqTable, - mysql.ErrAccessDenied: mysql.ErrAccessDenied, - codeTableLocked: mysql.ErrTableLocked, + mysql.ErrDBCreateExists: mysql.ErrDBCreateExists, + mysql.ErrDBDropExists: mysql.ErrDBDropExists, + mysql.ErrAccessDenied: mysql.ErrAccessDenied, + mysql.ErrBadDB: mysql.ErrBadDB, + mysql.ErrTableExists: mysql.ErrTableExists, + mysql.ErrBadTable: mysql.ErrBadTable, + mysql.ErrBadField: mysql.ErrBadField, + mysql.ErrDupFieldName: mysql.ErrDupFieldName, + mysql.ErrDupKeyName: mysql.ErrDupKeyName, + mysql.ErrNonuniqTable: mysql.ErrNonuniqTable, + mysql.ErrMultiplePriKey: mysql.ErrMultiplePriKey, + mysql.ErrTooManyKeyParts: mysql.ErrTooManyKeyParts, + mysql.ErrCantDropFieldOrKey: mysql.ErrCantDropFieldOrKey, + mysql.ErrTableNotLockedForWrite: mysql.ErrTableNotLockedForWrite, + mysql.ErrTableNotLocked: mysql.ErrTableNotLocked, + mysql.ErrNoSuchTable: mysql.ErrNoSuchTable, + mysql.ErrKeyDoesNotExist: mysql.ErrKeyDoesNotExist, + mysql.ErrCannotAddForeign: mysql.ErrCannotAddForeign, + mysql.ErrWrongFkDef: mysql.ErrWrongFkDef, + mysql.ErrDupIndex: mysql.ErrDupIndex, + mysql.ErrBadUser: mysql.ErrBadUser, + mysql.ErrUserAlreadyExists: mysql.ErrUserAlreadyExists, + mysql.ErrTableLocked: mysql.ErrTableLocked, } terror.ErrClassToMySQLCodes[terror.ClassSchema] = schemaMySQLErrCodes