🐛 fix(db): allow re-adding models & vendors after soft delete; add safe index cleanup

Replace legacy single-column unique indexes with composite unique indexes on
(name, deleted_at) and introduce a safe index drop utility to eliminate
duplicate-key errors and noisy MySQL 1091 warnings.

WHAT
• model/model_meta.go
  - Model.ModelName  → `uniqueIndex:uk_model_name,priority:1`
  - Model.DeletedAt  → `index; uniqueIndex:uk_model_name,priority:2`
• model/vendor_meta.go
  - Vendor.Name      → `uniqueIndex:uk_vendor_name,priority:1`
  - Vendor.DeletedAt → `index; uniqueIndex:uk_vendor_name,priority:2`
• model/main.go
  - Add `dropIndexIfExists(table, index)`:
    • Checks `information_schema.statistics`
    • Drops index only when present (avoids Error 1091)
  - Invoke helper in `migrateDB` & `migrateDBFast`
  - Remove direct `ALTER TABLE … DROP INDEX …` calls

WHY
• Users received `Error 1062 (23000)` when re-creating a soft-deleted
  model/vendor because the old unique index enforced uniqueness on name alone.
• Directly dropping nonexistent indexes caused MySQL `Error 1091` noise.

HOW
• Composite unique indexes `(model_name, deleted_at)` / `(name, deleted_at)`
  respect GORM soft deletes.
• Safe helper ensures idempotent migrations across environments.

RESULT
• Users can now delete and re-add the same model or vendor without manual SQL.
• Startup migration runs quietly across MySQL, PostgreSQL, and SQLite.
• No behavior changes for existing data beyond index updates.

TEST
1. Add model “deepseek-chat” → delete (soft) → re-add → success.
2. Add vendor “DeepSeek”     → delete (soft) → re-add → success.
3. Restart service twice → no duplicate key or 1091 errors.
This commit is contained in:
t0ng7u
2025-08-09 15:44:08 +08:00
parent a5cbef1a61
commit c776a1edff

View File

@@ -64,6 +64,22 @@ var DB *gorm.DB
var LOG_DB *gorm.DB
// dropIndexIfExists drops a MySQL index only if it exists to avoid noisy 1091 errors
func dropIndexIfExists(tableName string, indexName string) {
if !common.UsingMySQL {
return
}
var count int64
// Check index existence via information_schema
err := DB.Raw(
"SELECT COUNT(1) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?",
tableName, indexName,
).Scan(&count).Error
if err == nil && count > 0 {
_ = DB.Exec("ALTER TABLE " + tableName + " DROP INDEX " + indexName + ";").Error
}
}
func createRootAccountIfNeed() error {
var user User
//if user.Status != common.UserStatusEnabled {
@@ -236,11 +252,8 @@ func InitLogDB() (err error) {
func migrateDB() error {
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
if common.UsingMySQL {
// 旧索引可能不存在,忽略删除错误即可
_ = DB.Exec("ALTER TABLE models DROP INDEX uk_model_name;").Error
_ = DB.Exec("ALTER TABLE vendors DROP INDEX uk_vendor_name;").Error
}
dropIndexIfExists("models", "uk_model_name")
dropIndexIfExists("vendors", "uk_vendor_name")
if !common.UsingPostgreSQL {
return migrateDBFast()
}
@@ -271,10 +284,8 @@ func migrateDB() error {
func migrateDBFast() error {
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
if common.UsingMySQL {
_ = DB.Exec("ALTER TABLE models DROP INDEX uk_model_name;").Error
_ = DB.Exec("ALTER TABLE vendors DROP INDEX uk_vendor_name;").Error
}
dropIndexIfExists("models", "uk_model_name")
dropIndexIfExists("vendors", "uk_vendor_name")
var wg sync.WaitGroup