@@ -19,6 +19,7 @@ import (
1919 "time"
2020
2121 "github.com/go-mysql-org/go-mysql/replication"
22+ "github.com/pingcap/errors"
2223 "github.com/pingcap/failpoint"
2324 tidbddl "github.com/pingcap/tidb/pkg/ddl"
2425 "github.com/pingcap/tidb/pkg/meta/metabuild"
@@ -29,6 +30,7 @@ import (
2930 "github.com/pingcap/tidb/pkg/table"
3031 "github.com/pingcap/tidb/pkg/table/tables"
3132 "github.com/pingcap/tidb/pkg/types"
33+ "github.com/pingcap/tidb/pkg/util/dbterror"
3234 "github.com/pingcap/tidb/pkg/util/filter"
3335 tidbmock "github.com/pingcap/tidb/pkg/util/mock"
3436 regexprrouter "github.com/pingcap/tidb/pkg/util/regexpr-router"
@@ -1076,27 +1078,35 @@ func (ddl *DDLWorker) handleModifyColumn(qec *queryEventContext, info *ddlInfo,
10761078 if oldCol == nil {
10771079 return bf .AlterTable , nil
10781080 }
1079- newCol := table .ToColumn (& model.ColumnInfo {
1080- ID : oldCol .ID ,
1081- Offset : oldCol .Offset ,
1082- State : oldCol .State ,
1083- OriginDefaultValue : oldCol .OriginDefaultValue ,
1084- OriginDefaultValueBit : oldCol .OriginDefaultValueBit ,
1085- FieldType : * spec .NewColumns [0 ].Tp ,
1086- Name : spec .NewColumns [0 ].Name .Name ,
1087- Version : oldCol .Version ,
1088- })
10891081
1090- // handle charset and collation
1091- if err := tidbddl .ProcessColumnCharsetAndCollation (metabuild .NewContext (), oldCol , newCol , ti , spec .NewColumns [0 ], di ); err != nil {
1092- ddl .logger .Warn ("process column charset and collation failed" , zap .Error (err ))
1082+ // Let TiDB build the post-DDL column definition so we stay compatible with its internal
1083+ // modify-column handling even when lower-level helpers stop being exported.
1084+ mockCtx := tidbmock .NewContext ()
1085+ mockCtx .GetSessionVars ().AllowRemoveAutoInc = true
1086+ jobW , err := tidbddl .GetModifiableColumnJob (
1087+ qec .tctx .Ctx ,
1088+ mockCtx ,
1089+ nil ,
1090+ ast.Ident {Schema : di .Name , Name : ti .Name },
1091+ oldColumnName .Name ,
1092+ di ,
1093+ tbl ,
1094+ spec ,
1095+ )
1096+ if err != nil {
1097+ if et , ok := ddl .classifyUnsupportedModifyColumnEvent (info , oldCol , ti , di , spec .NewColumns [0 ], err ); ok {
1098+ return et , nil
1099+ }
1100+ ddl .logger .Warn ("build modifiable column job failed" , zap .Error (err ))
10931101 return bf .AlterTable , err
10941102 }
1095- // handle column options
1096- if err := tidbddl .ProcessModifyColumnOptions (tidbmock .NewContext (), newCol , spec .NewColumns [0 ].Options ); err != nil {
1097- ddl .logger .Warn ("process column options failed" , zap .Error (err ))
1103+ args , ok := jobW .JobArgs .(* model.ModifyColumnArgs )
1104+ if ! ok || args .Column == nil {
1105+ err := fmt .Errorf ("unexpected modify column job args: %T" , jobW .JobArgs )
1106+ ddl .logger .Warn ("get modifiable column failed" , zap .Error (err ))
10981107 return bf .AlterTable , err
10991108 }
1109+ newCol := ddl .normalizeModifyColumnEvent (info , oldCol , args .Column , spec .NewColumns [0 ])
11001110
11011111 if et := ddl .needChangeColumnData (oldCol , newCol ); et != bf .AlterTable {
11021112 return et , nil
@@ -1123,6 +1133,95 @@ func (ddl *DDLWorker) handleModifyColumn(qec *queryEventContext, info *ddlInfo,
11231133 }
11241134}
11251135
1136+ func (ddl * DDLWorker ) classifyUnsupportedModifyColumnEvent (
1137+ info * ddlInfo ,
1138+ oldCol * table.Column ,
1139+ ti * model.TableInfo ,
1140+ di * model.DBInfo ,
1141+ specNewColumn * ast.ColumnDef ,
1142+ jobErr error ,
1143+ ) (bf.EventType , bool ) {
1144+ cause := errors .Cause (jobErr )
1145+ if ! dbterror .ErrUnsupportedModifyCharset .Equal (cause ) && ! dbterror .ErrUnsupportedModifyCollation .Equal (cause ) {
1146+ return bf .AlterTable , false
1147+ }
1148+
1149+ // Newer TiDB rejects some MODIFY/CHANGE COLUMN charset transitions while DM still
1150+ // needs the historical event type so binlog-filter can block them before tracker/execution.
1151+ newCol := table .ToColumn (& model.ColumnInfo {
1152+ ID : oldCol .ID ,
1153+ Offset : oldCol .Offset ,
1154+ State : oldCol .State ,
1155+ OriginDefaultValue : oldCol .OriginDefaultValue ,
1156+ OriginDefaultValueBit : oldCol .OriginDefaultValueBit ,
1157+ FieldType : * specNewColumn .Tp ,
1158+ Name : specNewColumn .Name .Name ,
1159+ Version : oldCol .Version ,
1160+ })
1161+ if err := tidbddl .ProcessColumnCharsetAndCollation (metabuild .NewContext (), oldCol , newCol , ti , specNewColumn , di ); err != nil {
1162+ ddl .logger .Warn ("fallback classify modify column charset/collation failed" ,
1163+ zap .String ("origin_sql" , info .originDDL ),
1164+ zap .Error (jobErr ),
1165+ zap .Error (err ),
1166+ )
1167+ return bf .AlterTable , false
1168+ }
1169+
1170+ switch {
1171+ case oldCol .GetCharset () != newCol .GetCharset ():
1172+ return bf .ModifyCharset , true
1173+ case oldCol .GetCollate () != newCol .GetCollate ():
1174+ return bf .ModifyCollation , true
1175+ default :
1176+ return bf .AlterTable , false
1177+ }
1178+ }
1179+
1180+ func (ddl * DDLWorker ) normalizeModifyColumnEvent (
1181+ info * ddlInfo ,
1182+ oldCol * table.Column ,
1183+ jobCol * model.ColumnInfo ,
1184+ specNewColumn * ast.ColumnDef ,
1185+ ) * table.Column {
1186+ eventCol := table .ToColumn (jobCol .Clone ())
1187+
1188+ // TiDB's modify-column job builder preserves existing key flags so the DDL can
1189+ // execute correctly. DM's incompatible-DDL classification is historical and
1190+ // spec-based: omitted key attributes in CHANGE/MODIFY COLUMN should still be
1191+ // treated as removals for event typing.
1192+ const indexFlagMask = mysql .PriKeyFlag | mysql .UniqueKeyFlag | mysql .MultipleKeyFlag
1193+ eventCol .DelFlag (indexFlagMask )
1194+ eventCol .AddFlag (specNewColumn .Tp .GetFlag () & indexFlagMask )
1195+
1196+ hasExplicitDefault := false
1197+ for _ , opt := range specNewColumn .Options {
1198+ if opt .Tp == ast .ColumnOptionDefaultValue {
1199+ hasExplicitDefault = true
1200+ break
1201+ }
1202+ }
1203+ if ! hasExplicitDefault {
1204+ // Preserve the tracked schema's default when the statement does not specify
1205+ // one. This matches DM's previous event classification semantics and avoids
1206+ // misclassifying a key drop as a default-value change after earlier skipped DDLs.
1207+ eventCol .DefaultValue = oldCol .DefaultValue
1208+ eventCol .DefaultValueBit = oldCol .DefaultValueBit
1209+ eventCol .DefaultIsExpr = oldCol .DefaultIsExpr
1210+ }
1211+
1212+ ddl .logger .Debug ("normalized modify column event" ,
1213+ zap .String ("origin_sql" , info .originDDL ),
1214+ zap .Uint64 ("old_flags" , uint64 (oldCol .GetFlag ())),
1215+ zap .Uint64 ("job_flags" , uint64 (jobCol .GetFlag ())),
1216+ zap .Uint64 ("event_flags" , uint64 (eventCol .GetFlag ())),
1217+ zap .Any ("old_default" , oldCol .GetDefaultValue ()),
1218+ zap .Any ("job_default" , jobCol .GetDefaultValue ()),
1219+ zap .Any ("event_default" , eventCol .GetDefaultValue ()),
1220+ )
1221+
1222+ return eventCol
1223+ }
1224+
11261225// AstToDDLEvent returns filter.DDLEvent.
11271226func (ddl * DDLWorker ) AstToDDLEvent (qec * queryEventContext , info * ddlInfo ) (et bf.EventType ) {
11281227 defer func () {
0 commit comments