fix: preserve legacy time casts throughout index migrations
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user