From 60ff9707ee501786869d4d3bc4f0e795c48b2a27 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 19 Mar 2019 00:59:42 +0800 Subject: [PATCH 01/13] ddl: fix alter table charset bug and compatibility --- ddl/db_integration_test.go | 52 ++++++++++++++++++++++++++++++ ddl/ddl_api.go | 65 ++++++++++++++++++++++++++++---------- ddl/table.go | 21 ++++++++++++ ddl/table_test.go | 5 +++ 4 files changed, 126 insertions(+), 17 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index 6f69f2d9bc820..de9a972ec0d72 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -24,6 +24,7 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/errors" "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/charset" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" tmysql "github.com/pingcap/parser/mysql" @@ -40,6 +41,7 @@ import ( "github.com/pingcap/tidb/store/mockstore/mocktikv" "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/testkit" ) @@ -587,6 +589,56 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { rs, err = tk.Exec("alter table t charset utf8mb4 collate utf8mb4_bin") c.Assert(err, NotNil) + + // test change column charset when change table charset. + tk.MustExec("drop table t;") + tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8") + tk.MustExec("alter table t convert to charset utf8mb4;") + checkCharset := func() { + tbl := testGetTableByName(c, s.ctx, "test", "t") + c.Assert(tbl, NotNil) + c.Assert(tbl.Meta().Charset, Equals, charset.CharsetUTF8MB4) + c.Assert(tbl.Meta().Collate, Equals, charset.CollationUTF8MB4) + for _, col := range tbl.Meta().Columns { + c.Assert(col.Charset, Equals, charset.CharsetUTF8MB4) + c.Assert(col.Collate, Equals, charset.CollationUTF8MB4) + } + } + checkCharset() + // test when table charset is equal to target charset but column is not equal to the target charset. + tk.MustExec("drop table t;") + tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8mb4") + tk.MustExec("alter table t convert to charset utf8mb4;") + checkCharset() + + // Mock table info with charset is "". Old TiDB maybe create table with charset is "". + mockCtx := mock.NewContext() + mockCtx.Store = s.store + err = mockCtx.NewTxn(context.Background()) + c.Assert(err, IsNil) + txn, err := mockCtx.Txn(true) + c.Assert(err, IsNil) + mt := meta.NewMeta(txn) + db, ok := domain.GetDomain(s.ctx).InfoSchema().SchemaByName(model.NewCIStr("test")) + c.Assert(ok, IsTrue) + tbl := testGetTableByName(c, s.ctx, "test", "t") + tblInfo := tbl.Meta().Clone() + tblInfo.Charset = "" + tblInfo.Collate = "" + err = mt.UpdateTable(db.ID, tblInfo) + c.Assert(err, IsNil) + err = txn.Commit(context.Background()) + c.Assert(err, IsNil) + + // check table charset is "" + tk.MustExec("alter table t add column b varchar(10);") // load latest schema. + tbl = testGetTableByName(c, s.ctx, "test", "t") + c.Assert(tbl, NotNil) + c.Assert(tbl.Meta().Charset, Equals, "") + c.Assert(tbl.Meta().Collate, Equals, "") + // Test when table charset is "", this for compatibility. + tk.MustExec("alter table t convert to charset utf8mb4;") + checkCharset() } func (s *testIntegrationSuite) TestCaseInsensitiveCharsetAndCollate(c *C) { diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 1fd6d59db8bf7..cf61769f78455 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2426,12 +2426,9 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden if err != nil { return errors.Trace(infoschema.ErrTableNotExists.GenWithStackByArgs(ident.Schema, ident.Name)) } - - origCharset := tb.Meta().Charset - origCollate := tb.Meta().Collate if toCharset == "" { // charset does not change. - toCharset = origCharset + toCharset = tb.Meta().Charset } if toCollate == "" { @@ -2442,22 +2439,14 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden } } - if origCharset == toCharset && origCollate == toCollate { - // nothing to do. - return nil + doNothing, err := checkAlterTableCharsetAble(tb.Meta(), toCharset, toCollate) + if err != nil { + return err } - - if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { - return errors.Trace(err) + if doNothing { + return nil } - for _, col := range tb.Meta().Cols() { - if col.Tp == mysql.TypeVarchar { - if err = IsTooBigFieldLength(col.Flen, col.Name.O, toCharset); err != nil { - return errors.Trace(err) - } - } - } job := &model.Job{ SchemaID: schema.ID, TableID: tb.Meta().ID, @@ -2470,6 +2459,48 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden return errors.Trace(err) } +func checkAlterTableCharsetAble(tblInfo *model.TableInfo, toCharset, toCollate string) (bool, error) { + var err error + origCharset := tblInfo.Charset + origCollate := tblInfo.Collate + if origCharset == toCharset && origCollate == toCollate { + // nothing to do. + var doNothing = true + for _, col := range tblInfo.Columns { + if col.Charset == charset.CharsetBin { + continue + } + if col.Charset == toCharset && col.Collate == toCollate { + continue + } + doNothing = false + } + if doNothing { + return true, nil + } + } + + if len(origCharset) == 0 { + // The table charset may be "", if the table is create in old TiDB version. + // This DDL will update the table charset to default charset. + // use default charset or database charset? show create table is uses the default charset. + origCharset, origCollate = charset.GetDefaultCharsetAndCollate() + } + + if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { + return false, errors.Trace(err) + } + + for _, col := range tblInfo.Columns { + if col.Tp == mysql.TypeVarchar { + if err = IsTooBigFieldLength(col.Flen, col.Name.O, toCharset); err != nil { + return false, errors.Trace(err) + } + } + } + return false, nil +} + // RenameIndex renames an index. // In TiDB, indexes are case-insensitive (so index 'a' and 'A" are considered the same index), // but index names are case-sensitive (we can rename index 'a' to 'A') diff --git a/ddl/table.go b/ddl/table.go index a1e99a50ca647..9c94b07ed15ea 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -20,7 +20,9 @@ import ( "sync/atomic" "github.com/pingcap/errors" + "github.com/pingcap/parser/charset" "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" @@ -589,8 +591,27 @@ func onModifyTableCharsetAndCollate(t *meta.Meta, job *model.Job) (ver int64, _ return ver, errors.Trace(err) } + // double check. + _, err = checkAlterTableCharsetAble(tblInfo, toCharset, toCollate) + if err != nil { + job.State = model.JobStateCancelled + return ver, errors.Trace(err) + } + tblInfo.Charset = toCharset tblInfo.Collate = toCollate + // update column charset. + for _, col := range tblInfo.Columns { + switch col.Tp { + case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeEnum, mysql.TypeSet: + col.Charset = toCharset + col.Collate = toCollate + default: + col.Charset = charset.CharsetBin + col.Collate = charset.CharsetBin + } + } + ver, err = updateVersionAndTableInfo(t, job, tblInfo, true) if err != nil { return ver, errors.Trace(err) diff --git a/ddl/table_test.go b/ddl/table_test.go index f9a2cfbee3b56..18a3380e9b3b6 100644 --- a/ddl/table_test.go +++ b/ddl/table_test.go @@ -142,6 +142,11 @@ func testCreateTable(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, return job } +// TestCreateTable exports for integration test to uses. +func CreateTableForTest(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo) *model.Job { + return testCreateTable(c, ctx, d, dbInfo, tblInfo) +} + func testCreateView(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo) *model.Job { job := &model.Job{ SchemaID: dbInfo.ID, From 6ed875d1cb302baafec685377ffc8b079ced81ee Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 19 Mar 2019 10:17:26 +0800 Subject: [PATCH 02/13] refine code --- ddl/db_integration_test.go | 4 ++-- ddl/ddl_api.go | 7 ++++--- ddl/table.go | 2 +- ddl/table_test.go | 5 ----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index de9a972ec0d72..5f71f26452c85 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -590,7 +590,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { rs, err = tk.Exec("alter table t charset utf8mb4 collate utf8mb4_bin") c.Assert(err, NotNil) - // test change column charset when change table charset. + // Test change column charset when change table charset. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8") tk.MustExec("alter table t convert to charset utf8mb4;") @@ -605,7 +605,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { } } checkCharset() - // test when table charset is equal to target charset but column is not equal to the target charset. + // Test when table charset is equal to target charset but column charset is not equal. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8mb4") tk.MustExec("alter table t convert to charset utf8mb4;") diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index cf61769f78455..d97f3126ea98f 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2439,7 +2439,7 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden } } - doNothing, err := checkAlterTableCharsetAble(tb.Meta(), toCharset, toCollate) + doNothing, err := checkAlterTableCharset(tb.Meta(), toCharset, toCollate) if err != nil { return err } @@ -2459,7 +2459,7 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden return errors.Trace(err) } -func checkAlterTableCharsetAble(tblInfo *model.TableInfo, toCharset, toCollate string) (bool, error) { +func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate string) (bool, error) { var err error origCharset := tblInfo.Charset origCollate := tblInfo.Collate @@ -2483,7 +2483,8 @@ func checkAlterTableCharsetAble(tblInfo *model.TableInfo, toCharset, toCollate s if len(origCharset) == 0 { // The table charset may be "", if the table is create in old TiDB version. // This DDL will update the table charset to default charset. - // use default charset or database charset? show create table is uses the default charset. + // Use default charset or database charset? + // show create table is uses the default charset. origCharset, origCollate = charset.GetDefaultCharsetAndCollate() } diff --git a/ddl/table.go b/ddl/table.go index 9c94b07ed15ea..09eb0298dcbce 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -592,7 +592,7 @@ func onModifyTableCharsetAndCollate(t *meta.Meta, job *model.Job) (ver int64, _ } // double check. - _, err = checkAlterTableCharsetAble(tblInfo, toCharset, toCollate) + _, err = checkAlterTableCharset(tblInfo, toCharset, toCollate) if err != nil { job.State = model.JobStateCancelled return ver, errors.Trace(err) diff --git a/ddl/table_test.go b/ddl/table_test.go index 18a3380e9b3b6..f9a2cfbee3b56 100644 --- a/ddl/table_test.go +++ b/ddl/table_test.go @@ -142,11 +142,6 @@ func testCreateTable(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, return job } -// TestCreateTable exports for integration test to uses. -func CreateTableForTest(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo) *model.Job { - return testCreateTable(c, ctx, d, dbInfo, tblInfo) -} - func testCreateView(c *C, ctx sessionctx.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo) *model.Job { job := &model.Job{ SchemaID: dbInfo.ID, From 051abaab521de86f2402a9bb1254c840b369a397 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 19 Mar 2019 21:00:22 +0800 Subject: [PATCH 03/13] address comment --- ddl/ddl_api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index d97f3126ea98f..0e56da6854b17 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2465,7 +2465,7 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin origCollate := tblInfo.Collate if origCharset == toCharset && origCollate == toCollate { // nothing to do. - var doNothing = true + doNothing := true for _, col := range tblInfo.Columns { if col.Charset == charset.CharsetBin { continue @@ -2489,13 +2489,13 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin } if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { - return false, errors.Trace(err) + return false, err } for _, col := range tblInfo.Columns { if col.Tp == mysql.TypeVarchar { if err = IsTooBigFieldLength(col.Flen, col.Name.O, toCharset); err != nil { - return false, errors.Trace(err) + return false, err } } } From 78ef367b81f275a109f8dd6564b4dce69caa0eff Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 19 Mar 2019 21:08:19 +0800 Subject: [PATCH 04/13] address comment --- ddl/ddl_api.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index c8a5b2ee9ca73..7cf3d4886d67c 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2461,13 +2461,12 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden return errors.Trace(err) } -func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate string) (bool, error) { - var err error +func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate string) (doNothing bool, err error) { origCharset := tblInfo.Charset origCollate := tblInfo.Collate if origCharset == toCharset && origCollate == toCollate { // nothing to do. - doNothing := true + doNothing = true for _, col := range tblInfo.Columns { if col.Charset == charset.CharsetBin { continue @@ -2478,7 +2477,7 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin doNothing = false } if doNothing { - return true, nil + return doNothing, err } } @@ -2491,17 +2490,17 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin } if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { - return false, err + return doNothing, err } for _, col := range tblInfo.Columns { if col.Tp == mysql.TypeVarchar { if err = IsTooBigFieldLength(col.Flen, col.Name.O, toCharset); err != nil { - return false, err + return doNothing, err } } } - return false, nil + return doNothing, nil } // RenameIndex renames an index. From 552deb2b47ee3f53f7d29d88e9f900ecc6b463d2 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 19 Mar 2019 21:10:27 +0800 Subject: [PATCH 05/13] address comment --- ddl/ddl_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 7cf3d4886d67c..579b8c0306d3a 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2500,7 +2500,7 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin } } } - return doNothing, nil + return doNothing, err } // RenameIndex renames an index. From 81addf7e4041e4c21a843d02d0fa5773e3a97a22 Mon Sep 17 00:00:00 2001 From: crazycs Date: Wed, 20 Mar 2019 16:52:08 +0800 Subject: [PATCH 06/13] add comment --- ddl/ddl_api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 579b8c0306d3a..ada270581d0d8 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2461,6 +2461,10 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden return errors.Trace(err) } +// checkAlterTableCharset uses to check is it possible to change the charset of table. +// This function returns 2 variable: +// doNothing: if doNothing is true, means no need to change any more, because the target charset is same with the charset of table. +// err: if err is not nil, means it is not possible to change table charset to target charset. func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate string) (doNothing bool, err error) { origCharset := tblInfo.Charset origCollate := tblInfo.Collate From 260e21f588336c422669fc568fcfaad1186c554e Mon Sep 17 00:00:00 2001 From: crazycs Date: Thu, 21 Mar 2019 23:41:18 +0800 Subject: [PATCH 07/13] address comment --- ddl/ddl_api.go | 30 ++++++++++++++++++++---------- ddl/rollingback.go | 2 +- ddl/schema.go | 4 ++-- ddl/table.go | 13 ++++++++----- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index ada270581d0d8..40a63df8199d6 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -223,18 +223,27 @@ func ResolveCharsetCollation(tblCharset, dbCharset string) (string, string, erro return charset, collate, nil } +func typesNeedCharset(tp byte) bool { + switch tp { + case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, + mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, + mysql.TypeEnum, mysql.TypeSet: + return true + } + return false +} + func setCharsetCollationFlenDecimal(tp *types.FieldType, tblCharset string, dbCharset string) error { tp.Charset = strings.ToLower(tp.Charset) tp.Collate = strings.ToLower(tp.Collate) if len(tp.Charset) == 0 { - switch tp.Tp { - case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeEnum, mysql.TypeSet: + if typesNeedCharset(tp.Tp) { var err error tp.Charset, tp.Collate, err = ResolveCharsetCollation(tblCharset, dbCharset) if err != nil { return errors.Trace(err) } - default: + } else { tp.Charset = charset.CharsetBin tp.Collate = charset.CharsetBin } @@ -2441,7 +2450,7 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden } } - doNothing, err := checkAlterTableCharset(tb.Meta(), toCharset, toCollate) + doNothing, err := checkAlterTableCharset(tb.Meta(), schema, toCharset, toCollate) if err != nil { return err } @@ -2465,7 +2474,7 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden // This function returns 2 variable: // doNothing: if doNothing is true, means no need to change any more, because the target charset is same with the charset of table. // err: if err is not nil, means it is not possible to change table charset to target charset. -func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate string) (doNothing bool, err error) { +func checkAlterTableCharset(tblInfo *model.TableInfo, dbInfo *model.DBInfo, toCharset, toCollate string) (doNothing bool, err error) { origCharset := tblInfo.Charset origCollate := tblInfo.Collate if origCharset == toCharset && origCollate == toCollate { @@ -2481,16 +2490,17 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin doNothing = false } if doNothing { - return doNothing, err + return doNothing, nil } } if len(origCharset) == 0 { // The table charset may be "", if the table is create in old TiDB version. // This DDL will update the table charset to default charset. - // Use default charset or database charset? - // show create table is uses the default charset. - origCharset, origCollate = charset.GetDefaultCharsetAndCollate() + origCharset, origCollate, err = ResolveCharsetCollation("", dbInfo.Charset) + if err != nil { + return doNothing, err + } } if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { @@ -2504,7 +2514,7 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, toCharset, toCollate strin } } } - return doNothing, err + return doNothing, nil } // RenameIndex renames an index. diff --git a/ddl/rollingback.go b/ddl/rollingback.go index 1da54bd981c5a..ca886d48f16ac 100644 --- a/ddl/rollingback.go +++ b/ddl/rollingback.go @@ -193,7 +193,7 @@ func rollingbackDropTablePartition(t *meta.Meta, job *model.Job) (ver int64, err } func rollingbackDropSchema(t *meta.Meta, job *model.Job) error { - dbInfo, err := checkDropSchema(t, job) + dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) if err != nil { return errors.Trace(err) } diff --git a/ddl/schema.go b/ddl/schema.go index 3660c7268c14f..2012f7b3cb0bb 100644 --- a/ddl/schema.go +++ b/ddl/schema.go @@ -71,7 +71,7 @@ func onCreateSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { } func onDropSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { - dbInfo, err := checkDropSchema(t, job) + dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) if err != nil { return ver, errors.Trace(err) } @@ -120,7 +120,7 @@ func onDropSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { return ver, errors.Trace(err) } -func checkDropSchema(t *meta.Meta, job *model.Job) (*model.DBInfo, error) { +func checkSchemaExistAndCancelNonExistJob(t *meta.Meta, job *model.Job) (*model.DBInfo, error) { dbInfo, err := t.GetDatabase(job.SchemaID) if err != nil { return nil, errors.Trace(err) diff --git a/ddl/table.go b/ddl/table.go index 45d30c5139e3d..f1dfb5500fd2c 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -22,7 +22,6 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/parser/charset" "github.com/pingcap/parser/model" - "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" @@ -587,13 +586,18 @@ func onModifyTableCharsetAndCollate(t *meta.Meta, job *model.Job) (ver int64, _ return ver, errors.Trace(err) } + dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) + if err != nil { + return ver, errors.Trace(err) + } + tblInfo, err := getTableInfoAndCancelFaultJob(t, job, job.SchemaID) if err != nil { return ver, errors.Trace(err) } // double check. - _, err = checkAlterTableCharset(tblInfo, toCharset, toCollate) + _, err = checkAlterTableCharset(tblInfo, dbInfo, toCharset, toCollate) if err != nil { job.State = model.JobStateCancelled return ver, errors.Trace(err) @@ -603,11 +607,10 @@ func onModifyTableCharsetAndCollate(t *meta.Meta, job *model.Job) (ver int64, _ tblInfo.Collate = toCollate // update column charset. for _, col := range tblInfo.Columns { - switch col.Tp { - case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeEnum, mysql.TypeSet: + if typesNeedCharset(col.Tp) { col.Charset = toCharset col.Collate = toCollate - default: + } else { col.Charset = charset.CharsetBin col.Collate = charset.CharsetBin } From 322f9a631a4cbd7d57b479b46a246bdc81ce3e6d Mon Sep 17 00:00:00 2001 From: crazycs Date: Fri, 22 Mar 2019 00:05:10 +0800 Subject: [PATCH 08/13] add test --- ddl/db_integration_test.go | 53 ++++++++++++++++++++++++++++---------- ddl/ddl_api.go | 9 +++++++ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index 5f71f26452c85..796543cb7f94c 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -592,7 +592,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { // Test change column charset when change table charset. tk.MustExec("drop table t;") - tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8") + tk.MustExec("create table t(a varchar(10)) charset utf8") tk.MustExec("alter table t convert to charset utf8mb4;") checkCharset := func() { tbl := testGetTableByName(c, s.ctx, "test", "t") @@ -605,30 +605,42 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { } } checkCharset() - // Test when table charset is equal to target charset but column charset is not equal. + + // Test when column charset can not convert to the target charset. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10) character set ascii) charset utf8mb4") + _, err = tk.Exec("alter table t convert to charset utf8mb4;") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:210]unsupported modify charset from ascii to utf8mb4") + + // Test when table charset is equal to target charset but column charset is not equal. + tk.MustExec("drop table t;") + tk.MustExec("create table t(a varchar(10) character set utf8) charset utf8mb4") tk.MustExec("alter table t convert to charset utf8mb4;") checkCharset() // Mock table info with charset is "". Old TiDB maybe create table with charset is "". - mockCtx := mock.NewContext() - mockCtx.Store = s.store - err = mockCtx.NewTxn(context.Background()) - c.Assert(err, IsNil) - txn, err := mockCtx.Txn(true) - c.Assert(err, IsNil) - mt := meta.NewMeta(txn) db, ok := domain.GetDomain(s.ctx).InfoSchema().SchemaByName(model.NewCIStr("test")) c.Assert(ok, IsTrue) tbl := testGetTableByName(c, s.ctx, "test", "t") tblInfo := tbl.Meta().Clone() tblInfo.Charset = "" tblInfo.Collate = "" - err = mt.UpdateTable(db.ID, tblInfo) - c.Assert(err, IsNil) - err = txn.Commit(context.Background()) - c.Assert(err, IsNil) + updateTableInfo := func(tblInfo *model.TableInfo) { + mockCtx := mock.NewContext() + mockCtx.Store = s.store + err = mockCtx.NewTxn(context.Background()) + c.Assert(err, IsNil) + txn, err := mockCtx.Txn(true) + c.Assert(err, IsNil) + mt := meta.NewMeta(txn) + + err = mt.UpdateTable(db.ID, tblInfo) + c.Assert(err, IsNil) + err = txn.Commit(context.Background()) + c.Assert(err, IsNil) + } + updateTableInfo(tblInfo) // check table charset is "" tk.MustExec("alter table t add column b varchar(10);") // load latest schema. @@ -639,6 +651,21 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { // Test when table charset is "", this for compatibility. tk.MustExec("alter table t convert to charset utf8mb4;") checkCharset() + + // Test when column charset is "". + tbl = testGetTableByName(c, s.ctx, "test", "t") + tblInfo = tbl.Meta().Clone() + tblInfo.Columns[0].Charset = "" + tblInfo.Columns[0].Collate = "" + updateTableInfo(tblInfo) + // check table charset is "" + tk.MustExec("alter table t drop column b;") // load latest schema. + tbl = testGetTableByName(c, s.ctx, "test", "t") + c.Assert(tbl, NotNil) + c.Assert(tbl.Meta().Columns[0].Charset, Equals, "") + c.Assert(tbl.Meta().Columns[0].Collate, Equals, "") + tk.MustExec("alter table t convert to charset utf8mb4;") + checkCharset() } func (s *testIntegrationSuite) TestCaseInsensitiveCharsetAndCollate(c *C) { diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 40a63df8199d6..aff11f66eb115 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2513,6 +2513,15 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, dbInfo *model.DBInfo, toCh return doNothing, err } } + if col.Charset == charset.CharsetBin { + continue + } + if len(col.Charset) == 0 { + continue + } + if err = modifiableCharsetAndCollation(toCharset, toCollate, col.Charset, col.Collate); err != nil { + return doNothing, err + } } return doNothing, nil } From 834da81312f8ac23f76970f8ad9ec802af865728 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Tue, 26 Mar 2019 19:06:16 +0800 Subject: [PATCH 09/13] fix test and address comment --- ddl/db_integration_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index f899b0181ee4f..59ed811fddbe3 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -591,7 +591,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { rs, err = tk.Exec("alter table t charset utf8mb4 collate utf8mb4_bin") c.Assert(err, NotNil) - // Test change column charset when change table charset. + // Test change column charset when changing table charset. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10)) charset utf8") tk.MustExec("alter table t convert to charset utf8mb4;") @@ -1486,14 +1486,13 @@ func (s *testIntegrationSuite) TestIgnoreColumnUTF8Charset(c *C) { // Test for alter table convert charset config.GetGlobalConfig().TreatOldVersionUTF8AsUTF8MB4 = true - s.tk.MustExec("alter table t change column b b varchar(40) character set ascii") // reload schema. + s.tk.MustExec("alter table t drop column b") // reload schema. s.tk.MustExec("alter table t convert to charset utf8mb4;") config.GetGlobalConfig().TreatOldVersionUTF8AsUTF8MB4 = false - s.tk.MustExec("alter table t change column b b varchar(50) character set ascii") // reload schema. - // TODO: fix this after PR 9790. + s.tk.MustExec("alter table t add column b varchar(50);") // reload schema. s.tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + - " `a` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,\n" + - " `b` varchar(50) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL\n" + + " `a` varchar(20) DEFAULT NULL,\n" + + " `b` varchar(50) DEFAULT NULL\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) } From e87ad6b5dcc01301bba45ed993ca83973a6702b1 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Tue, 26 Mar 2019 20:41:19 +0800 Subject: [PATCH 10/13] add test and fix compatible problem --- ddl/db_integration_test.go | 17 +++++++++++++++++ ddl/ddl.go | 8 ++++++++ ddl/ddl_api.go | 31 +++++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index 59ed811fddbe3..95986e4f73ed5 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -570,6 +570,7 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { if rs != nil { rs.Close() } + c.Assert(err, NotNil) c.Assert(err.Error(), Equals, "Unknown charset gbk") rs, err = tk.Exec("alter table t charset utf8") if rs != nil { @@ -591,6 +592,22 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { rs, err = tk.Exec("alter table t charset utf8mb4 collate utf8mb4_bin") c.Assert(err, NotNil) + rs, err = tk.Exec("alter table t charset ''") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1115]Unknown character set: ''") + + rs, err = tk.Exec("alter table t collate ''") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1273]Unknown collation: ''") + + rs, err = tk.Exec("alter table t charset utf8mb4 collate '' collate utf8mb4_bin;") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1273]Unknown collation: ''") + + rs, err = tk.Exec("alter table t charset latin1 charset utf8 charset utf8mb4 collate utf8_bin;") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1302]Conflicting declarations: 'CHARACTER SET latin1' and 'CHARACTER SET utf8'") + // Test change column charset when changing table charset. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10)) charset utf8") diff --git a/ddl/ddl.go b/ddl/ddl.go index 1ef175d30b9ee..ed925efbedd89 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -180,6 +180,10 @@ var ( ErrWrongNameForIndex = terror.ClassDDL.New(codeWrongNameForIndex, mysql.MySQLErrName[mysql.ErrWrongNameForIndex]) // ErrUnknownCharacterSet returns unknown character set. ErrUnknownCharacterSet = terror.ClassDDL.New(codeUnknownCharacterSet, "Unknown character set: '%s'") + // ErrUnknownCharacterSet returns unknown character set. + ErrUnknownCollation = terror.ClassDDL.New(codeUnknownCollation, "Unknown collation: '%s'") + // ErrConflictingDeclarations + ErrConflictingDeclarations = terror.ClassDDL.New(codeConflictingDeclarations, "Conflicting declarations: 'CHARACTER SET %s' and 'CHARACTER SET %s'") // ErrPrimaryCantHaveNull returns All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead ErrPrimaryCantHaveNull = terror.ClassDDL.New(codePrimaryCantHaveNull, mysql.MySQLErrName[mysql.ErrPrimaryCantHaveNull]) @@ -688,6 +692,8 @@ const ( codeWrongNameForIndex = terror.ErrCode(mysql.ErrWrongNameForIndex) codeErrTooLongIndexComment = terror.ErrCode(mysql.ErrTooLongIndexComment) codeUnknownCharacterSet = terror.ErrCode(mysql.ErrUnknownCharacterSet) + codeUnknownCollation = terror.ErrCode(mysql.ErrUnknownCollation) + codeConflictingDeclarations = terror.ErrCode(mysql.ErrConflictingDeclarations) codeCantCreateTable = terror.ErrCode(mysql.ErrCantCreateTable) codeTableMustHaveColumns = terror.ErrCode(mysql.ErrTableMustHaveColumns) codePartitionsMustBeDefined = terror.ErrCode(mysql.ErrPartitionsMustBeDefined) @@ -747,6 +753,8 @@ func init() { codeErrTooLongIndexComment: mysql.ErrTooLongIndexComment, codeViewWrongList: mysql.ErrViewWrongList, codeUnknownCharacterSet: mysql.ErrUnknownCharacterSet, + codeUnknownCollation: mysql.ErrUnknownCollation, + codeConflictingDeclarations: mysql.ErrConflictingDeclarations, codePartitionsMustBeDefined: mysql.ErrPartitionsMustBeDefined, codePartitionMgmtOnNonpartitioned: mysql.ErrPartitionMgmtOnNonpartitioned, codeDropPartitionNonExistent: mysql.ErrDropPartitionNonExistent, diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 717e448dac825..1534994963a65 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -1584,9 +1584,9 @@ func isIgnorableSpec(tp ast.AlterTableType) bool { // getCharsetAndCollateInTableOption will iterate the charset and collate in the options, // and returns the last charset and collate in options. If there is no charset in the options, // the returns charset will be "", the same as collate. -func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) (charset, collate string) { - charsets := make([]string, len(options)) - collates := make([]string, len(options)) +func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) (charset, collate string, err error) { + charsets := make([]string, 0, len(options)) + collates := make([]string, 0, len(options)) for i := startIdx; i < len(options); i++ { opt := options[i] // we set the charset to the last option. example: alter table t charset latin1 charset utf8 collate utf8_bin; @@ -1599,11 +1599,21 @@ func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) } } - if len(charsets) != 0 { - charset = charsets[len(charsets)-1] + if len(charsets) > 1 { + return "", "", ErrConflictingDeclarations.GenWithStackByArgs(charsets[0], charsets[1]) + } + if len(charsets) == 1 { + if charsets[0] == "" { + return "", "", ErrUnknownCharacterSet.GenWithStackByArgs("") + } + charset = charsets[0] } - if len(collates) != 0 { + for i := range collates { + if collates[i] == "" { + return "", "", ErrUnknownCollation.GenWithStackByArgs("") + } + } collate = collates[len(collates)-1] } return @@ -1730,7 +1740,11 @@ func (d *ddl) AlterTable(ctx sessionctx.Context, ident ast.Ident, specs []*ast.A if handledCharsetOrCollate { continue } - toCharset, toCollate := getCharsetAndCollateInTableOption(i, spec.Options) + var toCharset, toCollate string + toCharset, toCollate, err = getCharsetAndCollateInTableOption(i, spec.Options) + if err != nil { + return err + } err = d.AlterTableCharsetAndCollate(ctx, ident, toCharset, toCollate) handledCharsetOrCollate = true } @@ -2097,7 +2111,7 @@ func (d *ddl) DropColumn(ctx sessionctx.Context, ti ast.Ident, colName model.CIS // modifiableCharsetAndCollation returns error when the charset or collation is not modifiable. func modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate string) error { if !charset.ValidCharsetAndCollation(toCharset, toCollate) { - return ErrUnknownCharacterSet.GenWithStackByArgs(toCharset, toCollate) + return ErrUnknownCharacterSet.GenWithStack("Unknown character set: '%s', collation: '%s'", toCharset, toCollate) } if toCharset == charset.CharsetUTF8MB4 && origCharset == charset.CharsetUTF8 { // TiDB only allow utf8 to be changed to utf8mb4. @@ -2546,6 +2560,7 @@ func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Iden if err != nil { return errors.Trace(infoschema.ErrTableNotExists.GenWithStackByArgs(ident.Schema, ident.Name)) } + if toCharset == "" { // charset does not change. toCharset = tb.Meta().Charset From 21897bdf4f05d8129033b5ceebdd5a665c907a25 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Tue, 26 Mar 2019 20:48:23 +0800 Subject: [PATCH 11/13] add comment --- ddl/ddl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddl/ddl.go b/ddl/ddl.go index ed925efbedd89..e2fdf52561e98 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -180,9 +180,9 @@ var ( ErrWrongNameForIndex = terror.ClassDDL.New(codeWrongNameForIndex, mysql.MySQLErrName[mysql.ErrWrongNameForIndex]) // ErrUnknownCharacterSet returns unknown character set. ErrUnknownCharacterSet = terror.ClassDDL.New(codeUnknownCharacterSet, "Unknown character set: '%s'") - // ErrUnknownCharacterSet returns unknown character set. + // ErrUnknownCollation returns unknown collation. ErrUnknownCollation = terror.ClassDDL.New(codeUnknownCollation, "Unknown collation: '%s'") - // ErrConflictingDeclarations + // ErrConflictingDeclarations return conflict declarations. ErrConflictingDeclarations = terror.ClassDDL.New(codeConflictingDeclarations, "Conflicting declarations: 'CHARACTER SET %s' and 'CHARACTER SET %s'") // ErrPrimaryCantHaveNull returns All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead ErrPrimaryCantHaveNull = terror.ClassDDL.New(codePrimaryCantHaveNull, mysql.MySQLErrName[mysql.ErrPrimaryCantHaveNull]) From 8de39eb8d95dfda54428a032fe4fe64d0ee4dd47 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Wed, 27 Mar 2019 15:44:08 +0800 Subject: [PATCH 12/13] add comment --- ddl/ddl_api.go | 2 +- ddl/rollingback.go | 2 +- ddl/schema.go | 4 ++-- ddl/table.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 1534994963a65..55bfd407a968f 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2620,7 +2620,7 @@ func checkAlterTableCharset(tblInfo *model.TableInfo, dbInfo *model.DBInfo, toCh } if len(origCharset) == 0 { - // The table charset may be "", if the table is create in old TiDB version. + // The table charset may be "", if the table is create in old TiDB version, such as v2.0.8. // This DDL will update the table charset to default charset. origCharset, origCollate, err = ResolveCharsetCollation("", dbInfo.Charset) if err != nil { diff --git a/ddl/rollingback.go b/ddl/rollingback.go index ca886d48f16ac..730e5b5d6689b 100644 --- a/ddl/rollingback.go +++ b/ddl/rollingback.go @@ -193,7 +193,7 @@ func rollingbackDropTablePartition(t *meta.Meta, job *model.Job) (ver int64, err } func rollingbackDropSchema(t *meta.Meta, job *model.Job) error { - dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) + dbInfo, err := checkSchemaExistAndCancelNotExistJob(t, job) if err != nil { return errors.Trace(err) } diff --git a/ddl/schema.go b/ddl/schema.go index 2012f7b3cb0bb..ac875e158059b 100644 --- a/ddl/schema.go +++ b/ddl/schema.go @@ -71,7 +71,7 @@ func onCreateSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { } func onDropSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { - dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) + dbInfo, err := checkSchemaExistAndCancelNotExistJob(t, job) if err != nil { return ver, errors.Trace(err) } @@ -120,7 +120,7 @@ func onDropSchema(t *meta.Meta, job *model.Job) (ver int64, _ error) { return ver, errors.Trace(err) } -func checkSchemaExistAndCancelNonExistJob(t *meta.Meta, job *model.Job) (*model.DBInfo, error) { +func checkSchemaExistAndCancelNotExistJob(t *meta.Meta, job *model.Job) (*model.DBInfo, error) { dbInfo, err := t.GetDatabase(job.SchemaID) if err != nil { return nil, errors.Trace(err) diff --git a/ddl/table.go b/ddl/table.go index f1dfb5500fd2c..253d2482af2ae 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -586,7 +586,7 @@ func onModifyTableCharsetAndCollate(t *meta.Meta, job *model.Job) (ver int64, _ return ver, errors.Trace(err) } - dbInfo, err := checkSchemaExistAndCancelNonExistJob(t, job) + dbInfo, err := checkSchemaExistAndCancelNotExistJob(t, job) if err != nil { return ver, errors.Trace(err) } From 17313a70e499a5b07ed73adb9c01a77208e38645 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Fri, 29 Mar 2019 14:11:06 +0800 Subject: [PATCH 13/13] address comment --- ddl/db_integration_test.go | 8 ++++++++ ddl/ddl.go | 4 ++++ ddl/ddl_api.go | 9 ++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index 95986e4f73ed5..d9b4bc5fb9b51 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -608,6 +608,14 @@ func (s *testIntegrationSuite) TestChangingTableCharset(c *C) { c.Assert(err, NotNil) c.Assert(err.Error(), Equals, "[ddl:1302]Conflicting declarations: 'CHARACTER SET latin1' and 'CHARACTER SET utf8'") + rs, err = tk.Exec("alter table t charset utf8 collate utf8mb4_bin;") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1253]COLLATION 'utf8mb4_bin' is not valid for CHARACTER SET 'utf8'") + + rs, err = tk.Exec("alter table t charset utf8 collate utf8_bin collate utf8mb4_bin collate utf8_bin;") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:1253]COLLATION 'utf8mb4_bin' is not valid for CHARACTER SET 'utf8'") + // Test change column charset when changing table charset. tk.MustExec("drop table t;") tk.MustExec("create table t(a varchar(10)) charset utf8") diff --git a/ddl/ddl.go b/ddl/ddl.go index e2fdf52561e98..c21ac119c4def 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -182,6 +182,8 @@ var ( ErrUnknownCharacterSet = terror.ClassDDL.New(codeUnknownCharacterSet, "Unknown character set: '%s'") // ErrUnknownCollation returns unknown collation. ErrUnknownCollation = terror.ClassDDL.New(codeUnknownCollation, "Unknown collation: '%s'") + // ErrCollationCharsetMismatch returns when collation not match the charset. + ErrCollationCharsetMismatch = terror.ClassDDL.New(codeCollationCharsetMismatch, mysql.MySQLErrName[mysql.ErrCollationCharsetMismatch]) // ErrConflictingDeclarations return conflict declarations. ErrConflictingDeclarations = terror.ClassDDL.New(codeConflictingDeclarations, "Conflicting declarations: 'CHARACTER SET %s' and 'CHARACTER SET %s'") // ErrPrimaryCantHaveNull returns All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead @@ -693,6 +695,7 @@ const ( codeErrTooLongIndexComment = terror.ErrCode(mysql.ErrTooLongIndexComment) codeUnknownCharacterSet = terror.ErrCode(mysql.ErrUnknownCharacterSet) codeUnknownCollation = terror.ErrCode(mysql.ErrUnknownCollation) + codeCollationCharsetMismatch = terror.ErrCode(mysql.ErrCollationCharsetMismatch) codeConflictingDeclarations = terror.ErrCode(mysql.ErrConflictingDeclarations) codeCantCreateTable = terror.ErrCode(mysql.ErrCantCreateTable) codeTableMustHaveColumns = terror.ErrCode(mysql.ErrTableMustHaveColumns) @@ -754,6 +757,7 @@ func init() { codeViewWrongList: mysql.ErrViewWrongList, codeUnknownCharacterSet: mysql.ErrUnknownCharacterSet, codeUnknownCollation: mysql.ErrUnknownCollation, + codeCollationCharsetMismatch: mysql.ErrCollationCharsetMismatch, codeConflictingDeclarations: mysql.ErrConflictingDeclarations, codePartitionsMustBeDefined: mysql.ErrPartitionsMustBeDefined, codePartitionMgmtOnNonpartitioned: mysql.ErrPartitionMgmtOnNonpartitioned, diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 55bfd407a968f..38e1a0acc7b2d 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -1584,7 +1584,7 @@ func isIgnorableSpec(tp ast.AlterTableType) bool { // getCharsetAndCollateInTableOption will iterate the charset and collate in the options, // and returns the last charset and collate in options. If there is no charset in the options, // the returns charset will be "", the same as collate. -func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) (charset, collate string, err error) { +func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) (ca, co string, err error) { charsets := make([]string, 0, len(options)) collates := make([]string, 0, len(options)) for i := startIdx; i < len(options); i++ { @@ -1606,15 +1606,18 @@ func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) if charsets[0] == "" { return "", "", ErrUnknownCharacterSet.GenWithStackByArgs("") } - charset = charsets[0] + ca = charsets[0] } if len(collates) != 0 { for i := range collates { if collates[i] == "" { return "", "", ErrUnknownCollation.GenWithStackByArgs("") } + if len(ca) != 0 && !charset.ValidCharsetAndCollation(ca, collates[i]) { + return "", "", ErrCollationCharsetMismatch.GenWithStackByArgs(collates[i], ca) + } } - collate = collates[len(collates)-1] + co = collates[len(collates)-1] } return }