80 lines
3.2 KiB
Go
80 lines
3.2 KiB
Go
package business
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 保留旧 register_time 字符串及其 CAST 语义,用虚拟列索引避免逐行扫描主账号。
|
|
// MySQL 可将查询中相同的 CAST 表达式匹配到该索引,既有查询无需更改统计口径。
|
|
func migrateRegistrationIndex(db *gorm.DB) error {
|
|
if db.Dialector.Name() != "mysql" || !db.Migrator().HasTable("tab_user") {
|
|
return nil
|
|
}
|
|
const column = "sw_register_time_unix"
|
|
const index = "idx_sw_user_registration"
|
|
if !db.Migrator().HasColumn("tab_user", column) {
|
|
if err := db.Exec("ALTER TABLE tab_user ADD COLUMN sw_register_time_unix BIGINT UNSIGNED GENERATED ALWAYS AS (CAST(register_time AS UNSIGNED)) VIRTUAL, ALGORITHM=INPLACE, LOCK=NONE").Error; err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
var expression string
|
|
if err := db.Raw("SELECT generation_expression FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='tab_user' AND column_name=?", column).Scan(&expression).Error; err != nil {
|
|
return err
|
|
}
|
|
normalized := strings.NewReplacer("`", "", " ", "", "\n", "", "\t", "").Replace(strings.ToLower(expression))
|
|
if normalized != "cast(register_timeasunsigned)" {
|
|
return fmt.Errorf("tab_user.%s 的现有定义与注册时间转换不符", column)
|
|
}
|
|
}
|
|
if !db.Migrator().HasIndex("tab_user", index) {
|
|
return addRegistrationIndex(db)
|
|
}
|
|
var columns []string
|
|
if err := db.Raw("SELECT column_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='tab_user' AND index_name=? ORDER BY seq_in_index", index).Scan(&columns).Error; err != nil {
|
|
return err
|
|
}
|
|
if strings.Join(columns, ",") != "source_db,puid,sw_register_time_unix,id" {
|
|
return fmt.Errorf("tab_user.%s 的现有索引列不符", index)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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 := fresh().Raw("SELECT @@session.sql_mode").Scan(&previous).Error; err != nil {
|
|
return err
|
|
}
|
|
modes := []string{}
|
|
for _, mode := range strings.Split(previous, ",") {
|
|
if mode != "STRICT_TRANS_TABLES" && mode != "STRICT_ALL_TABLES" {
|
|
modes = append(modes, mode)
|
|
}
|
|
}
|
|
if err := fresh().Exec("SET SESSION sql_mode=?", strings.Join(modes, ",")).Error; err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := fresh().Exec("SET SESSION sql_mode=?", previous).Error; err != nil {
|
|
result = errors.Join(result, err)
|
|
}
|
|
}()
|
|
return migrate(fresh())
|
|
})
|
|
}
|