fix: preserve legacy time casts throughout index migrations

This commit is contained in:
Yvan
2026-11-06 01:44:57 +08:00
parent bf67626830
commit 7c8148a5e5
3 changed files with 65 additions and 5 deletions
@@ -221,6 +221,47 @@ func TestMySQLRegistrationIndexPreservesCastAndUsesRange(t *testing.T) {
}
}
func TestMySQLLaterRegistrationIndexesPreserveMalformedLegacyTimes(t *testing.T) {
db := openRehearsalMySQL(t)
for _, sql := range []string{
"CREATE TABLE tab_user(id INT,source_db VARCHAR(10),puid INT,promote_id INT,register_time VARCHAR(20),PRIMARY KEY(id,source_db))",
"INSERT INTO tab_user VALUES(1,'lx',0,10,''),(2,'lx',0,10,'bad'),(3,'lx',0,10,'1793635200 ')",
} {
if err := db.Exec(sql).Error; err != nil {
t.Fatal(err)
}
}
pool, _ := db.DB()
pool.SetMaxOpenConns(1)
var mode string
if err := db.Raw("SELECT @@session.sql_mode").Scan(&mode).Error; err != nil {
t.Fatal(err)
}
if err := migrateRegistrationIndex(db); err != nil {
t.Fatal(err)
}
indexes := []businessQueryIndex{{"tab_user", "idx_alert_user_window", []string{"source_db", "promote_id", "sw_register_time_unix", "puid"}}}
for i := 0; i < 2; i++ {
if err := migrateQueryIndexes(db, indexes); err != nil {
t.Fatal(err)
}
}
var after string
if err := db.Raw("SELECT @@session.sql_mode").Scan(&after).Error; err != nil || mode != after {
t.Fatalf("sql_mode changed: %v", err)
}
var values []string
if err := db.Table("tab_user").Order("id").Pluck("register_time", &values).Error; err != nil || strings.Join(values, "|") != "|bad|1793635200 " {
t.Fatalf("original strings changed: %q %v", values, err)
}
if err := migrateQueryIndexes(db, []businessQueryIndex{{"tab_user", "idx_invalid_column", []string{"missing_column"}}}); err == nil {
t.Fatal("invalid DDL unexpectedly succeeded")
}
if err := db.Raw("SELECT @@session.sql_mode").Scan(&after).Error; err != nil || mode != after {
t.Fatalf("failed DDL did not restore sql_mode: %v", err)
}
}
func TestMySQLChannelCreationSerializesAccount(t *testing.T) {
db := openRehearsalMySQL(t)
if err := db.AutoMigrate(&platformChannelPO{}, &channelSettlementPO{}); err != nil {
+11 -1
View File
@@ -74,7 +74,17 @@ func migrateQueryIndexes(db *gorm.DB, indexes []businessQueryIndex) error {
}
// 同一大表的多个索引合并构建,减少重复扫描。
if len(additions) > 0 {
if err := db.Exec("ALTER TABLE `" + table + "` " + strings.Join(additions, ",") + ", ALGORITHM=INPLACE, LOCK=NONE").Error; err != nil {
build := func(conn *gorm.DB) error {
return conn.Exec("ALTER TABLE `" + table + "` " + strings.Join(additions, ",") + ", ALGORITHM=INPLACE, LOCK=NONE").Error
}
// Later indexes also evaluate the historical generated time column.
// Apply the same connection-local compatibility rule as its first index.
if table == "tab_user" && db.Migrator().HasColumn(table, "sw_register_time_unix") {
err = withLegacyRegistrationCast(db, build)
} else {
err = build(db)
}
if err != nil {
return fmt.Errorf("add query indexes on %s: %w", table, err)
}
}
+13 -4
View File
@@ -44,11 +44,20 @@ func migrateRegistrationIndex(db *gorm.DB) error {
}
func addRegistrationIndex(db *gorm.DB) error {
return withLegacyRegistrationCast(db, func(conn *gorm.DB) error {
return conn.Exec("ALTER TABLE tab_user ADD INDEX idx_sw_user_registration(source_db,puid,sw_register_time_unix,id), ALGORITHM=INPLACE, LOCK=NONE").Error
})
}
func withLegacyRegistrationCast(db *gorm.DB, migrate func(*gorm.DB) error) error {
// 旧 SELECT CAST 会以警告方式接受尾部空格等历史值。建索引属于 DDL,
// 严格模式会把同一警告升级为错误;仅在固定连接的索引构建期间保持旧转换行为。
return db.Connection(func(conn *gorm.DB) (result error) {
// Keep the bound connection's base handle free of statement errors so
// restoration still executes after a failed ALTER TABLE.
fresh := func() *gorm.DB { return conn.Session(&gorm.Session{NewDB: true}) }
var previous string
if err := conn.Raw("SELECT @@session.sql_mode").Scan(&previous).Error; err != nil {
if err := fresh().Raw("SELECT @@session.sql_mode").Scan(&previous).Error; err != nil {
return err
}
modes := []string{}
@@ -57,14 +66,14 @@ func addRegistrationIndex(db *gorm.DB) error {
modes = append(modes, mode)
}
}
if err := conn.Exec("SET SESSION sql_mode=?", strings.Join(modes, ",")).Error; err != nil {
if err := fresh().Exec("SET SESSION sql_mode=?", strings.Join(modes, ",")).Error; err != nil {
return err
}
defer func() {
if err := conn.Exec("SET SESSION sql_mode=?", previous).Error; err != nil {
if err := fresh().Exec("SET SESSION sql_mode=?", previous).Error; err != nil {
result = errors.Join(result, err)
}
}()
return conn.Exec("ALTER TABLE tab_user ADD INDEX idx_sw_user_registration(source_db,puid,sw_register_time_unix,id), ALGORITHM=INPLACE, LOCK=NONE").Error
return migrate(fresh())
})
}