@@ -21,6 +21,7 @@ import (
2121 "github.com/pingcap/tidb/pkg/parser/ast"
2222 _ "github.com/pingcap/tidb/pkg/types/parser_driver"
2323 "go.opentelemetry.io/otel/attribute"
24+ "go.opentelemetry.io/otel/metric"
2425 "go.opentelemetry.io/otel/trace"
2526 "golang.org/x/text/encoding"
2627 "google.golang.org/protobuf/proto"
@@ -590,7 +591,8 @@ func (c *MySqlConnector) PullRecords(
590591 for _ , stmt := range stmts {
591592 if alterTableStmt , ok := stmt .(* ast.AlterTableStmt ); ok {
592593 if err := c .processAlterTableQuery (
593- ctx , catalogPool , req , alterTableStmt , string (ev .Schema ), binlogRowMetadataSupported , req .InternalVersion ); err != nil {
594+ ctx , catalogPool , otelManager , req , alterTableStmt ,
595+ string (ev .Schema ), binlogRowMetadataSupported , req .InternalVersion ); err != nil {
594596 return fmt .Errorf ("failed to process ALTER TABLE query: %w" , err )
595597 }
596598 }
@@ -669,7 +671,7 @@ func (c *MySqlConnector) PullRecords(
669671 if ev .Table .ColumnName != nil {
670672 var err error
671673 fields , err = c .processTableMapEventSchema (
672- ctx , catalogPool , req , ev .Table ,
674+ ctx , catalogPool , otelManager , req , ev .Table ,
673675 sourceTableName , destinationTableName , schema , exclusion ,
674676 )
675677 if err != nil {
@@ -828,6 +830,7 @@ func (c *MySqlConnector) PullRecords(
828830}
829831
830832func (c * MySqlConnector ) processAlterTableQuery (ctx context.Context , catalogPool shared.CatalogPool ,
833+ otelManager * otel_metrics.OtelManager ,
831834 req * model.PullRecordsRequest [model.RecordItems ], stmt * ast.AlterTableStmt , stmtSchema string ,
832835 binlogRowMetadataSupported bool , mirrorVersion uint32 ,
833836) error {
@@ -855,6 +858,13 @@ func (c *MySqlConnector) processAlterTableQuery(ctx context.Context, catalogPool
855858 NullableEnabled : currentSchema != nil && currentSchema .NullableEnabled ,
856859 }
857860
861+ existingColTypes := make (map [string ]string )
862+ if currentSchema != nil {
863+ for _ , col := range currentSchema .Columns {
864+ existingColTypes [col .Name ] = col .Type
865+ }
866+ }
867+
858868 hasPositionShiftingDdlChanges := false
859869
860870 for _ , spec := range stmt .Specs {
@@ -869,18 +879,28 @@ func (c *MySqlConnector) processAlterTableQuery(ctx context.Context, catalogPool
869879 continue
870880 }
871881
882+ qkind , err := QkindFromMysqlColumnType (col .Tp .InfoSchemaStr (), binlogRowMetadataSupported , mirrorVersion )
883+ if err != nil {
884+ return err
885+ }
886+
887+ if oldType , exists := existingColTypes [col .Name .OrigColName ()]; exists && oldType != string (qkind ) {
888+ c .logger .Warn ("column type change detected via ALTER TABLE, not propagating" ,
889+ slog .String ("table" , sourceTableName ),
890+ slog .String ("column" , col .Name .OrigColName ()),
891+ slog .String ("from" , oldType ),
892+ slog .String ("to" , string (qkind )))
893+ c .recordColumnTypeChange (ctx , otelManager , types .QValueKind (oldType ), qkind ,
894+ otel_metrics .SourceEventTypeDDL )
895+ }
896+
872897 if spec .Position != nil && spec .Position .Tp != ast .ColumnPositionNone {
873898 hasPositionShiftingDdlChanges = true
874899 c .logger .Warn ("column added with position specifier (FIRST/AFTER)" ,
875900 slog .String ("columnName" , col .Name .String ()),
876901 slog .String ("tableName" , sourceTableName ))
877902 }
878903
879- qkind , err := QkindFromMysqlColumnType (col .Tp .InfoSchemaStr (), binlogRowMetadataSupported , mirrorVersion )
880- if err != nil {
881- return err
882- }
883-
884904 nullable := true
885905 for _ , option := range col .Options {
886906 if option .Tp == ast .ColumnOptionNotNull {
@@ -956,12 +976,25 @@ func parseIncidentEvent(data []byte) (uint16, string) {
956976 return incident , string (data [3 :end ])
957977}
958978
979+ func (c * MySqlConnector ) recordColumnTypeChange (
980+ ctx context.Context , otelManager * otel_metrics.OtelManager , from types.QValueKind , to types.QValueKind ,
981+ eventType string ,
982+ ) {
983+ otelManager .Metrics .ColumnTypeChangesCounter .Add (ctx , 1 , metric .WithAttributeSet (attribute .NewSet (
984+ attribute .String (otel_metrics .SourcePeerType , "mysql" ),
985+ attribute .String (otel_metrics .TypeChangeFromKey , string (from )),
986+ attribute .String (otel_metrics .TypeChangeToKey , string (to )),
987+ attribute .String (otel_metrics .SourceEventTypeKey , eventType ),
988+ )))
989+ }
990+
959991// processTableMapEventSchema compares the TABLE_MAP_EVENT schema against the cached schema
960992// and returns a TableSchemaDelta if new columns are detected (e.g., after gh-ost migration).
961993// It also returns a slice mapping binlog column index to FieldDescription for efficient row processing.
962994func (c * MySqlConnector ) processTableMapEventSchema (
963995 ctx context.Context ,
964996 catalogPool shared.CatalogPool ,
997+ otelManager * otel_metrics.OtelManager ,
965998 req * model.PullRecordsRequest [model.RecordItems ],
966999 tableMap * replication.TableMapEvent ,
9671000 sourceTableName string ,
@@ -989,14 +1022,26 @@ func (c *MySqlConnector) processTableMapEventSchema(
9891022 continue
9901023 }
9911024
1025+ var charset uint16
1026+ if collation , ok := collationMap [idx ]; ok {
1027+ charset = uint16 (collation )
1028+ }
1029+
9921030 if fd , exists := existingCols [colName ]; exists {
9931031 newFds [idx ] = fd
1032+
1033+ if qkind , err := qkindFromMysqlType (
1034+ tableMap .ColumnType [idx ], unsignedMap [idx ], charset , req .InternalVersion ,
1035+ ); err == nil && qkind != types .QValueKind (fd .Type ) {
1036+ c .logger .Warn ("column type change detected from TABLE_MAP_EVENT, not propagating" ,
1037+ slog .String ("table" , sourceTableName ),
1038+ slog .String ("column" , colName ),
1039+ slog .String ("from" , fd .Type ),
1040+ slog .String ("to" , string (qkind )))
1041+ c .recordColumnTypeChange (ctx , otelManager , types .QValueKind (fd .Type ), qkind , otel_metrics .SourceEventTypeEventMetadata )
1042+ }
9941043 } else {
9951044 // New column detected - get type from TABLE_MAP_EVENT
996- var charset uint16
997- if collation , ok := collationMap [idx ]; ok {
998- charset = uint16 (collation )
999- }
10001045 mytype := tableMap .ColumnType [idx ]
10011046 qkind , err := qkindFromMysqlType (mytype , unsignedMap [idx ], charset , req .InternalVersion )
10021047 if err != nil {
0 commit comments