🐛 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:
@@ -64,6 +64,22 @@ var DB *gorm.DB
|
|||||||
|
|
||||||
var LOG_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 {
|
func createRootAccountIfNeed() error {
|
||||||
var user User
|
var user User
|
||||||
//if user.Status != common.UserStatusEnabled {
|
//if user.Status != common.UserStatusEnabled {
|
||||||
@@ -236,11 +252,8 @@ func InitLogDB() (err error) {
|
|||||||
|
|
||||||
func migrateDB() error {
|
func migrateDB() error {
|
||||||
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
|
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
|
||||||
if common.UsingMySQL {
|
dropIndexIfExists("models", "uk_model_name")
|
||||||
// 旧索引可能不存在,忽略删除错误即可
|
dropIndexIfExists("vendors", "uk_vendor_name")
|
||||||
_ = DB.Exec("ALTER TABLE models DROP INDEX uk_model_name;").Error
|
|
||||||
_ = DB.Exec("ALTER TABLE vendors DROP INDEX uk_vendor_name;").Error
|
|
||||||
}
|
|
||||||
if !common.UsingPostgreSQL {
|
if !common.UsingPostgreSQL {
|
||||||
return migrateDBFast()
|
return migrateDBFast()
|
||||||
}
|
}
|
||||||
@@ -271,10 +284,8 @@ func migrateDB() error {
|
|||||||
|
|
||||||
func migrateDBFast() error {
|
func migrateDBFast() error {
|
||||||
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
|
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
|
||||||
if common.UsingMySQL {
|
dropIndexIfExists("models", "uk_model_name")
|
||||||
_ = DB.Exec("ALTER TABLE models DROP INDEX uk_model_name;").Error
|
dropIndexIfExists("vendors", "uk_vendor_name")
|
||||||
_ = DB.Exec("ALTER TABLE vendors DROP INDEX uk_vendor_name;").Error
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user