-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ddl: support altering the other charset to utf8 or utf8mb4 #8037
Changes from 20 commits
e882210
c0ec35d
a54cb1e
7627a0a
c1da1fa
1069ffe
d5421ad
e3fd359
a80e568
e479c8d
2d991a4
bc7b5e3
13ebfc5
8032e69
2869497
3869585
ba963c2
e6df64f
5d2e89f
aeba21e
d4a9027
b15d3b1
b4dce09
c1e004a
9cb2d5d
36681fa
a114ea4
aa53216
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1035,6 +1035,17 @@ func (d *ddl) handleAutoIncID(tbInfo *model.TableInfo, schemaID int64) error { | |
return nil | ||
} | ||
|
||
func setDefaultTableCharsetAndCollation(tbInfo *model.TableInfo) (err error) { | ||
if len(tbInfo.Charset) == 0 { | ||
tiancaiamao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tbInfo.Charset = mysql.DefaultCharset | ||
} | ||
|
||
if len(tbInfo.Collate) == 0 { | ||
tbInfo.Collate, err = charset.GetDefaultCollation(tbInfo.Charset) | ||
} | ||
return | ||
} | ||
|
||
// handleTableOptions updates tableInfo according to table options. | ||
func handleTableOptions(options []*ast.TableOption, tbInfo *model.TableInfo) error { | ||
for _, op := range options { | ||
|
@@ -1059,6 +1070,8 @@ func handleTableOptions(options []*ast.TableOption, tbInfo *model.TableInfo) err | |
} | ||
} | ||
} | ||
|
||
setDefaultTableCharsetAndCollation(tbInfo) | ||
return nil | ||
} | ||
|
||
|
@@ -1078,6 +1091,28 @@ func isIgnorableSpec(tp ast.AlterTableType) bool { | |
return tp == ast.AlterTableLock || tp == ast.AlterTableAlgorithm | ||
} | ||
|
||
// getCharsetAndCollateInTableOption will iterate the charset and collate in the options, | ||
// and returns the last charset and collate in options. | ||
func getCharsetAndCollateInTableOption(startIdx int, options []*ast.TableOption) (charset, collate string) { | ||
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; | ||
// the charset will be utf8, collate will be utf8_bin | ||
switch opt.Tp { | ||
case ast.TableOptionCharset: | ||
charset = opt.StrValue | ||
// this opt is handled, so we can skipped in the next iteration. | ||
options[i].Skipped = true | ||
case ast.TableOptionCollate: | ||
collate = opt.StrValue | ||
// this opt is handled, so we can skipped in the next iteration. | ||
options[i].Skipped = true | ||
} | ||
} | ||
|
||
return charset, collate | ||
} | ||
|
||
func (d *ddl) AlterTable(ctx sessionctx.Context, ident ast.Ident, specs []*ast.AlterTableSpec) (err error) { | ||
// Only handle valid specs. | ||
validSpecs := make([]*ast.AlterTableSpec, 0, len(specs)) | ||
|
@@ -1141,7 +1176,11 @@ func (d *ddl) AlterTable(ctx sessionctx.Context, ident ast.Ident, specs []*ast.A | |
case ast.AlterTableRenameIndex: | ||
err = d.RenameIndex(ctx, ident, spec) | ||
case ast.AlterTableOption: | ||
for _, opt := range spec.Options { | ||
for i, opt := range spec.Options { | ||
if opt.Skipped { | ||
continue | ||
} | ||
|
||
switch opt.Tp { | ||
case ast.TableOptionShardRowID: | ||
if opt.UintValue > shardRowIDBitsMax { | ||
|
@@ -1153,7 +1192,11 @@ func (d *ddl) AlterTable(ctx sessionctx.Context, ident ast.Ident, specs []*ast.A | |
case ast.TableOptionComment: | ||
spec.Comment = opt.StrValue | ||
err = d.AlterTableComment(ctx, ident, spec) | ||
case ast.TableOptionCharset, ast.TableOptionCollate: | ||
toCharset, toCollate := getCharsetAndCollateInTableOption(i, spec.Options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is weird to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed. |
||
err = d.AlterTableCharsetAndCollate(ctx, ident, toCharset, toCollate) | ||
} | ||
|
||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
|
@@ -1492,6 +1535,29 @@ func (d *ddl) DropColumn(ctx sessionctx.Context, ti ast.Ident, colName model.CIS | |
return errors.Trace(err) | ||
} | ||
|
||
// 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) | ||
} | ||
|
||
if toCharset == charset.CharsetUTF8MB4 || (toCharset == charset.CharsetUTF8 && origCharset != charset.CharsetUTF8MB4) { | ||
// TiDB treats all the data as utf8mb4, so we support changing the charset to utf8mb4. | ||
// And not allow to change utf8mb4 to utf8. | ||
return nil | ||
} | ||
|
||
if toCharset != origCharset { | ||
msg := fmt.Sprintf("charset from %s to %s", origCharset, toCharset) | ||
return errUnsupportedModifyCharset.GenWithStackByArgs(msg) | ||
} | ||
if toCollate != origCollate { | ||
msg := fmt.Sprintf("collate from %s to %s", origCollate, toCollate) | ||
return errUnsupportedModifyCharset.GenWithStackByArgs(msg) | ||
} | ||
return nil | ||
} | ||
|
||
// modifiable checks if the 'origin' type can be modified to 'to' type with out the need to | ||
// change or check existing data in the table. | ||
// It returns true if the two types has the same Charset and Collation, the same sign, both are | ||
|
@@ -1505,14 +1571,10 @@ func modifiable(origin *types.FieldType, to *types.FieldType) error { | |
msg := fmt.Sprintf("decimal %d is less than origin %d", to.Decimal, origin.Decimal) | ||
return errUnsupportedModifyColumn.GenWithStackByArgs(msg) | ||
} | ||
if to.Charset != origin.Charset { | ||
msg := fmt.Sprintf("charset %s not match origin %s", to.Charset, origin.Charset) | ||
return errUnsupportedModifyColumn.GenWithStackByArgs(msg) | ||
} | ||
if to.Collate != origin.Collate { | ||
msg := fmt.Sprintf("collate %s not match origin %s", to.Collate, origin.Collate) | ||
return errUnsupportedModifyColumn.GenWithStackByArgs(msg) | ||
if err := modifiableCharsetAndCollation(to.Charset, to.Collate, origin.Charset, origin.Collate); err != nil { | ||
return errors.Trace(err) | ||
} | ||
|
||
toUnsigned := mysql.HasUnsignedFlag(to.Flag) | ||
originUnsigned := mysql.HasUnsignedFlag(origin.Flag) | ||
if originUnsigned != toUnsigned { | ||
|
@@ -1875,6 +1937,59 @@ func (d *ddl) AlterTableComment(ctx sessionctx.Context, ident ast.Ident, spec *a | |
return errors.Trace(err) | ||
} | ||
|
||
// AlterTableCharset changes the table charset and collate. | ||
func (d *ddl) AlterTableCharsetAndCollate(ctx sessionctx.Context, ident ast.Ident, toCharset string, toCollate string) error { | ||
if toCharset == "" && toCollate == "" { | ||
return errors.Errorf("toCharset and toCollate can't be empty") | ||
zimulala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
is := d.infoHandle.Get() | ||
schema, ok := is.SchemaByName(ident.Schema) | ||
if !ok { | ||
return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(ident.Schema) | ||
} | ||
|
||
tb, err := is.TableByName(ident.Schema, ident.Name) | ||
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 | ||
} | ||
|
||
if toCollate == "" { | ||
// get the default collation of the charset. | ||
toCollate, err = charset.GetDefaultCollation(toCharset) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
} | ||
|
||
if origCharset == toCharset && origCollate == toCollate { | ||
// nothing to do. | ||
return nil | ||
} | ||
|
||
if err = modifiableCharsetAndCollation(toCharset, toCollate, origCharset, origCollate); err != nil { | ||
return errors.Trace(err) | ||
} | ||
|
||
job := &model.Job{ | ||
SchemaID: schema.ID, | ||
TableID: tb.Meta().ID, | ||
Type: model.ActionModifyTableCharsetAndCollate, | ||
BinlogInfo: &model.HistoryInfo{}, | ||
Args: []interface{}{toCharset, toCollate}, | ||
} | ||
err = d.doDDLJob(ctx, job) | ||
err = d.callHookOnChanged(err) | ||
return errors.Trace(err) | ||
} | ||
|
||
// 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') | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems the code above is TiDB special. I think we need some time to move it to the other place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.