119 lines
5.7 KiB
Go
119 lines
5.7 KiB
Go
package business
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
func TestMySQLBusinessQueryIndexesAreAdditiveAndIdempotent(t *testing.T) {
|
|
db := openRehearsalMySQL(t)
|
|
definitions := map[string]string{
|
|
"tab_promote": "id BIGINT NOT NULL,source_db VARCHAR(10) NOT NULL,create_time BIGINT,PRIMARY KEY(id,source_db)",
|
|
"tab_spend": "id BIGINT NOT NULL,source_db VARCHAR(10) NOT NULL,promote_id BIGINT,pay_status INT,pay_time BIGINT,pay_amount DECIMAL(20,2),PRIMARY KEY(id,source_db)",
|
|
"tab_sw_report_day": "dedup_mode INT,stat_date DATE,source_db VARCHAR(10),promote_id BIGINT,game_id BIGINT,total_pay DECIMAL(20,2)",
|
|
"tab_sw_report_day_key": "dedup_mode INT,stat_date DATE,source_db VARCHAR(10),promote_id BIGINT,game_id BIGINT,metric INT,identity_key VARCHAR(64)",
|
|
"tab_sw_report_server_day": "game_name VARCHAR(64),dedup_mode INT,stat_date DATE,source_db VARCHAR(10),game_id BIGINT,server_num VARCHAR(32),total_pay DECIMAL(20,2)",
|
|
"tab_sw_report_server_day_key": "game_name VARCHAR(64),dedup_mode INT,stat_date DATE,source_db VARCHAR(10),game_id BIGINT,server_num VARCHAR(32),metric INT,identity_key VARCHAR(64)",
|
|
}
|
|
for table, columns := range definitions {
|
|
if err := db.Exec("CREATE TABLE `" + table + "` (" + columns + ") ENGINE=InnoDB").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := db.Exec("INSERT INTO tab_spend VALUES(77,'lx',55,1,123456,12.30)").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for attempt := 0; attempt < 2; attempt++ {
|
|
if err := migrateBusinessQueryIndexes(db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, index := range businessQueryIndexes {
|
|
var columns []string
|
|
if err := db.Raw("SELECT column_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name=? AND index_name=? ORDER BY seq_in_index", index.table, index.name).Scan(&columns).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Join(columns, ",") != strings.Join(index.columns, ",") {
|
|
t.Fatalf("index %s has unexpected columns: %v", index.name, columns)
|
|
}
|
|
}
|
|
var amount string
|
|
if err := db.Raw("SELECT pay_amount FROM tab_spend WHERE id=77 AND source_db='lx'").Scan(&amount).Error; err != nil || amount != "12.30" {
|
|
t.Fatalf("index migration changed business data: %s, %v", amount, err)
|
|
}
|
|
if err := db.Exec("ALTER TABLE tab_spend DROP INDEX idx_sw_spend_time_summary, ADD INDEX idx_sw_spend_time_summary(pay_time)").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateBusinessQueryIndexes(db); err == nil || !strings.Contains(err.Error(), "idx_sw_spend_time_summary") {
|
|
t.Fatalf("must reject a conflicting existing index: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMySQLReportBuildIndexesCoverInputsAndPreserveIdentities(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,idcard VARCHAR(20),payload VARCHAR(1000),PRIMARY KEY(id,source_db)) ENGINE=InnoDB",
|
|
"CREATE TABLE tab_user_play_info(id INT PRIMARY KEY,play_time INT,user_id INT,source_db VARCHAR(10),promote_id INT,game_id INT,server_id VARCHAR(30),payload VARCHAR(1000),KEY(play_time)) ENGINE=InnoDB",
|
|
} {
|
|
if err := db.Exec(sql).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
users, plays := []map[string]any{}, []map[string]any{}
|
|
known := map[business.ReportUserKey]bool{}
|
|
ids := []int64{}
|
|
for id := int64(1); id <= 4000; id++ {
|
|
for _, source := range []string{"lx", "hw"} {
|
|
users = append(users, map[string]any{"id": id, "source_db": source, "puid": 0, "promote_id": id, "idcard": source, "payload": strings.Repeat("x", 300)})
|
|
}
|
|
plays = append(plays, map[string]any{"id": id, "play_time": id, "user_id": id, "source_db": "lx", "promote_id": id, "game_id": 1, "server_id": " 001 ", "payload": strings.Repeat("x", 300)})
|
|
if id <= 501 {
|
|
known[business.ReportUserKey{SourceDB: "lx", ID: id}] = true
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
for table, rows := range map[string][]map[string]any{"tab_user": users, "tab_user_play_info": plays} {
|
|
if err := db.Table(table).CreateInBatches(rows, 200).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for attempt := 0; attempt < 2; attempt++ {
|
|
if err := migrateReportBuildQueryIndexes(db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
loaded, err := loadReportUsers(db, known, map[int64]bool{501: true})
|
|
if err != nil || len(loaded) != 502 {
|
|
t.Fatalf("分批读取或跨平台歧义匹配错误: rows=%d error=%v", len(loaded), err)
|
|
}
|
|
for key, user := range loaded {
|
|
if user.IDCard != key.SourceDB || user.PromoteID == nil || *user.PromoteID != key.ID {
|
|
t.Fatalf("迁移改变用户归属或身份: %+v", key)
|
|
}
|
|
}
|
|
for _, query := range []struct {
|
|
sql, index string
|
|
args []any
|
|
}{
|
|
{"SELECT id,source_db,puid,promote_id,idcard FROM tab_user WHERE source_db=? AND id IN ?", "idx_sw_user_report_identity", []any{"lx", ids}},
|
|
{"SELECT id,source_db,puid,promote_id,idcard FROM tab_user WHERE id IN ?", "idx_sw_user_report_identity", []any{ids}},
|
|
{"SELECT user_id,source_db,promote_id,game_id,server_id FROM tab_user_play_info WHERE server_id<>'' AND play_time BETWEEN ? AND ?", "idx_sw_play_report_active", []any{1, 501}},
|
|
} {
|
|
var plan []struct{ Key, Type, Extra string }
|
|
if err := db.Raw("EXPLAIN "+query.sql, query.args...).Scan(&plan).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(plan) != 1 || plan[0].Key != query.index || plan[0].Type != "range" || !strings.Contains(plan[0].Extra, "Using index") {
|
|
t.Fatalf("任务查询未使用覆盖范围索引: %+v", plan)
|
|
}
|
|
}
|
|
if err := db.Exec("ALTER TABLE tab_user DROP INDEX idx_sw_user_report_identity, ADD INDEX idx_sw_user_report_identity(id)").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateReportBuildQueryIndexes(db); err == nil || !strings.Contains(err.Error(), "idx_sw_user_report_identity") {
|
|
t.Fatalf("同名但列不符的索引必须拒绝: %v", err)
|
|
}
|
|
}
|