Files
kra-new/internal/data/business/query_indexes_test.go
T

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)
}
}