Skip to content

Commit 13ba8b2

Browse files
AilinKidsre-bot
authored andcommitted
DDL: fix a bug in column charset and collate when create table and modify column #11300 (#11423)
All tests passed, auto merged by Bot
1 parent f299fb2 commit 13ba8b2

File tree

2 files changed

+136
-12
lines changed

2 files changed

+136
-12
lines changed

ddl/ddl_api.go

+68-12
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,34 @@ func typesNeedCharset(tp byte) bool {
288288
return false
289289
}
290290

291-
func setCharsetCollationFlenDecimal(tp *types.FieldType, tblCharset string, dbCharset string) error {
291+
func setCharsetCollationFlenDecimal(tp *types.FieldType, specifiedCollates []string, tblCharset string, dbCharset string) error {
292292
tp.Charset = strings.ToLower(tp.Charset)
293293
tp.Collate = strings.ToLower(tp.Collate)
294294
if len(tp.Charset) == 0 {
295295
if typesNeedCharset(tp.Tp) {
296-
var err error
297-
tp.Charset, tp.Collate, err = ResolveCharsetCollation(tblCharset, dbCharset)
298-
if err != nil {
299-
return errors.Trace(err)
296+
if len(specifiedCollates) == 0 {
297+
// Both the charset and collate are not specified.
298+
var err error
299+
tp.Charset, tp.Collate, err = ResolveCharsetCollation(tblCharset, dbCharset)
300+
if err != nil {
301+
return errors.Trace(err)
302+
}
303+
} else {
304+
// The charset is not specified but the collate is.
305+
// We should derive charset from it's collate specified rather than getting from table and db.
306+
// It is handled like mysql's logic, use derived charset to judge conflict with next collate.
307+
for _, spc := range specifiedCollates {
308+
derivedCollation, err := charset.GetCollationByName(spc)
309+
if err != nil {
310+
return errors.Trace(err)
311+
}
312+
if len(tp.Charset) == 0 {
313+
tp.Charset = derivedCollation.CharsetName
314+
} else if tp.Charset != derivedCollation.CharsetName {
315+
return ErrCollationCharsetMismatch.GenWithStackByArgs(derivedCollation.Name, tp.Charset)
316+
}
317+
tp.Collate = derivedCollation.Name
318+
}
300319
}
301320
} else {
302321
tp.Charset = charset.CharsetBin
@@ -307,10 +326,25 @@ func setCharsetCollationFlenDecimal(tp *types.FieldType, tblCharset string, dbCh
307326
return errUnsupportedCharset.GenWithStackByArgs(tp.Charset, tp.Collate)
308327
}
309328
if len(tp.Collate) == 0 {
310-
var err error
311-
tp.Collate, err = charset.GetDefaultCollation(tp.Charset)
312-
if err != nil {
313-
return errors.Trace(err)
329+
if len(specifiedCollates) == 0 {
330+
// The charset is specified, but the collate is not.
331+
var err error
332+
tp.Collate, err = charset.GetDefaultCollation(tp.Charset)
333+
if err != nil {
334+
return errors.Trace(err)
335+
}
336+
} else {
337+
// Both the charset and collate are specified.
338+
for _, spc := range specifiedCollates {
339+
derivedCollation, err := charset.GetCollationByName(spc)
340+
if err != nil {
341+
return errors.Trace(err)
342+
}
343+
if tp.Charset != derivedCollation.CharsetName {
344+
return ErrCollationCharsetMismatch.GenWithStackByArgs(derivedCollation.Name, tp.Charset)
345+
}
346+
tp.Collate = derivedCollation.Name
347+
}
314348
}
315349
}
316350
}
@@ -333,8 +367,10 @@ func setCharsetCollationFlenDecimal(tp *types.FieldType, tblCharset string, dbCh
333367
// outPriKeyConstraint is the primary key constraint out of column definition. such as: create table t1 (id int , age int, primary key(id));
334368
func buildColumnAndConstraint(ctx sessionctx.Context, offset int,
335369
colDef *ast.ColumnDef, outPriKeyConstraint *ast.Constraint, tblCharset, dbCharset string) (*table.Column, []*ast.Constraint, error) {
336-
err := setCharsetCollationFlenDecimal(colDef.Tp, tblCharset, dbCharset)
337-
if err != nil {
370+
// specifiedCollates refers to collates in colDef.Options, should handle them together.
371+
specifiedCollates := extractCollateFromOption(colDef)
372+
373+
if err := setCharsetCollationFlenDecimal(colDef.Tp, specifiedCollates, tblCharset, dbCharset); err != nil {
338374
return nil, nil, errors.Trace(err)
339375
}
340376
col, cts, err := columnDefToCol(ctx, offset, colDef, outPriKeyConstraint)
@@ -2025,7 +2061,11 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or
20252061
newCol.FieldType.Charset = col.FieldType.Charset
20262062
newCol.FieldType.Collate = col.FieldType.Collate
20272063
}
2028-
err = setCharsetCollationFlenDecimal(&newCol.FieldType, t.Meta().Charset, schema.Charset)
2064+
// specifiedCollates refers to collates in colDef.Option. When setting charset and collate here we
2065+
// should take the collate in colDef.Option into consideration rather than handling it separately
2066+
specifiedCollates := extractCollateFromOption(specNewColumn)
2067+
2068+
err = setCharsetCollationFlenDecimal(&newCol.FieldType, specifiedCollates, t.Meta().Charset, schema.Charset)
20292069
if err != nil {
20302070
return nil, errors.Trace(err)
20312071
}
@@ -2677,3 +2717,19 @@ func buildPartitionInfo(meta *model.TableInfo, d *ddl, spec *ast.AlterTableSpec)
26772717
}
26782718
return part, nil
26792719
}
2720+
2721+
// extractCollateFromOption take collates(may multiple) in option into consideration
2722+
// when handle charset and collate of a column, rather than handling it separately.
2723+
func extractCollateFromOption(def *ast.ColumnDef) []string {
2724+
specifiedCollates := make([]string, 0, 0)
2725+
for i := 0; i < len(def.Options); i++ {
2726+
op := def.Options[i]
2727+
if op.Tp == ast.ColumnOptionCollate {
2728+
specifiedCollates = append(specifiedCollates, op.StrValue)
2729+
def.Options = append(def.Options[:i], def.Options[i+1:]...)
2730+
// maintain the correct index
2731+
i--
2732+
}
2733+
}
2734+
return specifiedCollates
2735+
}

executor/ddl_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package executor_test
1515

1616
import (
1717
"fmt"
18+
"github.com/pingcap/tidb/ddl"
1819
"math"
1920
"strconv"
2021
"strings"
@@ -118,6 +119,36 @@ func (s *testSuite) TestCreateTable(c *C) {
118119
}
119120
}
120121

122+
// test multiple collate specified in column when create.
123+
tk.MustExec("drop table if exists test_multiple_column_collate;")
124+
tk.MustExec("create table test_multiple_column_collate (a char(1) collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
125+
t, err := domain.GetDomain(tk.Se).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("test_multiple_column_collate"))
126+
c.Assert(err, IsNil)
127+
c.Assert(t.Cols()[0].Charset, Equals, "utf8")
128+
c.Assert(t.Cols()[0].Collate, Equals, "utf8_general_ci")
129+
c.Assert(t.Meta().Charset, Equals, "utf8mb4")
130+
c.Assert(t.Meta().Collate, Equals, "utf8mb4_bin")
131+
132+
tk.MustExec("drop table if exists test_multiple_column_collate;")
133+
tk.MustExec("create table test_multiple_column_collate (a char(1) charset utf8 collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
134+
t, err = domain.GetDomain(tk.Se).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("test_multiple_column_collate"))
135+
c.Assert(err, IsNil)
136+
c.Assert(t.Cols()[0].Charset, Equals, "utf8")
137+
c.Assert(t.Cols()[0].Collate, Equals, "utf8_general_ci")
138+
c.Assert(t.Meta().Charset, Equals, "utf8mb4")
139+
c.Assert(t.Meta().Collate, Equals, "utf8mb4_bin")
140+
141+
// test Err case for multiple collate specified in column when create.
142+
tk.MustExec("drop table if exists test_err_multiple_collate;")
143+
_, err = tk.Exec("create table test_err_multiple_collate (a char(1) charset utf8mb4 collate utf8_unicode_ci collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
144+
c.Assert(err, NotNil)
145+
c.Assert(err.Error(), Equals, ddl.ErrCollationCharsetMismatch.GenWithStackByArgs("utf8_unicode_ci", "utf8mb4").Error())
146+
147+
tk.MustExec("drop table if exists test_err_multiple_collate;")
148+
_, err = tk.Exec("create table test_err_multiple_collate (a char(1) collate utf8_unicode_ci collate utf8mb4_general_ci) charset utf8mb4 collate utf8mb4_bin")
149+
c.Assert(err, NotNil)
150+
c.Assert(err.Error(), Equals, ddl.ErrCollationCharsetMismatch.GenWithStackByArgs("utf8mb4_general_ci", "utf8").Error())
151+
121152
// table option is auto-increment
122153
tk.MustExec("drop table if exists create_auto_increment_test;")
123154
tk.MustExec("create table create_auto_increment_test (id int not null auto_increment, name varchar(255), primary key(id)) auto_increment = 999;")
@@ -239,6 +270,43 @@ func (s *testSuite) TestAlterTableModifyColumn(c *C) {
239270
createSQL := result.Rows()[0][1]
240271
expected := "CREATE TABLE `mc` (\n `c1` bigint(20) DEFAULT NULL,\n `c2` text DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin"
241272
c.Assert(createSQL, Equals, expected)
273+
274+
// test multiple collate modification in column.
275+
tk.MustExec("drop table if exists modify_column_multiple_collate")
276+
tk.MustExec("create table modify_column_multiple_collate (a char(1) collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
277+
_, err = tk.Exec("alter table modify_column_multiple_collate modify column a char(1) collate utf8mb4_bin;")
278+
c.Assert(err, IsNil)
279+
t, err := domain.GetDomain(tk.Se).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("modify_column_multiple_collate"))
280+
c.Assert(err, IsNil)
281+
c.Assert(t.Cols()[0].Charset, Equals, "utf8mb4")
282+
c.Assert(t.Cols()[0].Collate, Equals, "utf8mb4_bin")
283+
c.Assert(t.Meta().Charset, Equals, "utf8mb4")
284+
c.Assert(t.Meta().Collate, Equals, "utf8mb4_bin")
285+
286+
tk.MustExec("drop table if exists modify_column_multiple_collate;")
287+
tk.MustExec("create table modify_column_multiple_collate (a char(1) collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
288+
_, err = tk.Exec("alter table modify_column_multiple_collate modify column a char(1) charset utf8mb4 collate utf8mb4_bin;")
289+
c.Assert(err, IsNil)
290+
t, err = domain.GetDomain(tk.Se).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("modify_column_multiple_collate"))
291+
c.Assert(err, IsNil)
292+
c.Assert(t.Cols()[0].Charset, Equals, "utf8mb4")
293+
c.Assert(t.Cols()[0].Collate, Equals, "utf8mb4_bin")
294+
c.Assert(t.Meta().Charset, Equals, "utf8mb4")
295+
c.Assert(t.Meta().Collate, Equals, "utf8mb4_bin")
296+
297+
// test Err case for multiple collate modification in column.
298+
tk.MustExec("drop table if exists err_modify_multiple_collate;")
299+
tk.MustExec("create table err_modify_multiple_collate (a char(1) collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
300+
_, err = tk.Exec("alter table err_modify_multiple_collate modify column a char(1) charset utf8mb4 collate utf8_bin;")
301+
c.Assert(err, NotNil)
302+
c.Assert(err.Error(), Equals, ddl.ErrCollationCharsetMismatch.GenWithStackByArgs("utf8_bin", "utf8mb4").Error())
303+
304+
tk.MustExec("drop table if exists err_modify_multiple_collate;")
305+
tk.MustExec("create table err_modify_multiple_collate (a char(1) collate utf8_bin collate utf8_general_ci) charset utf8mb4 collate utf8mb4_bin")
306+
_, err = tk.Exec("alter table err_modify_multiple_collate modify column a char(1) collate utf8_bin collate utf8mb4_bin;")
307+
c.Assert(err, NotNil)
308+
c.Assert(err.Error(), Equals, ddl.ErrCollationCharsetMismatch.GenWithStackByArgs("utf8mb4_bin", "utf8").Error())
309+
242310
}
243311

244312
func (s *testSuite) TestDefaultDBAfterDropCurDB(c *C) {

0 commit comments

Comments
 (0)