Răsfoiți Sursa

🐛 fix(db): allow re-adding models/vendors after soft delete via composite unique indexes

Ensure models and vendors can be re-created after soft deletion by switching to composite unique indexes on (name, deleted_at) and cleaning up legacy single-column unique indexes on MySQL.

Why
- MySQL raised 1062 duplicate key errors when re-adding a soft-deleted model/vendor because the legacy unique index enforced uniqueness on the name column alone (uk_model_name / uk_vendor_name), despite soft deletes.
- Users encountered errors such as:
  - Error 1062 (23000): Duplicate entry 'deepseek-chat' for key 'models.uk_model_name'
  - Error 1062 (23000): Duplicate entry 'DeepSeek' for key 'vendors.uk_vendor_name'

How
- Model indices:
  - model/model_meta.go:
    - Model.ModelName → gorm: uniqueIndex:uk_model_name,priority:1
    - Model.DeletedAt → gorm: index; uniqueIndex:uk_model_name,priority:2
- Vendor indices:
  - model/vendor_meta.go:
    - Vendor.Name → gorm: uniqueIndex:uk_vendor_name,priority:1
    - Vendor.DeletedAt → gorm: index; uniqueIndex:uk_vendor_name,priority:2
- Migration (automatic, idempotent):
  - model/main.go (migrateDB, migrateDBFast):
    - On MySQL, drop legacy single-column unique indexes if present:
      - ALTER TABLE models DROP INDEX uk_model_name;
      - ALTER TABLE vendors DROP INDEX uk_vendor_name;
    - Then run AutoMigrate to create composite unique indexes.
  - Missing-index errors are ignored to keep the migration safe to run multiple times.

Result
- Users can delete and re-add the same model/vendor name without manual SQL.
- Migration runs automatically at startup; no user action required.
- PostgreSQL and SQLite remain unaffected.

Files changed
- model/model_meta.go
- model/vendor_meta.go
- model/main.go (migrateDB, migrateDBFast)

Testing
- Create model "deepseek-chat" → delete (soft) → re-create → succeeds.
- Create vendor "DeepSeek" → delete (soft) → re-create → succeeds.

Backward compatibility
- Data remains intact; only index definitions are updated.
- Behavior is unchanged except for fixing the uniqueness constraint with soft deletes.
t0ng7u 4 luni în urmă
părinte
comite
8bccda5649
3 a modificat fișierele cu 16 adăugiri și 4 ștergeri
  1. 12 0
      model/main.go
  2. 2 2
      model/model_meta.go
  3. 2 2
      model/vendor_meta.go

+ 12 - 0
model/main.go

@@ -235,6 +235,12 @@ 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
+	}
 	if !common.UsingPostgreSQL {
 		return migrateDBFast()
 	}
@@ -264,6 +270,12 @@ 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
+	}
+
 	var wg sync.WaitGroup
 
 	migrations := []struct {

+ 2 - 2
model/model_meta.go

@@ -36,7 +36,7 @@ type BoundChannel struct {
 
 type Model struct {
     Id          int            `json:"id"`
-    ModelName   string         `json:"model_name" gorm:"size:128;not null;uniqueIndex:uk_model_name,where:deleted_at IS NULL"`
+    ModelName   string         `json:"model_name" gorm:"size:128;not null;uniqueIndex:uk_model_name,priority:1"`
     Description string         `json:"description,omitempty" gorm:"type:text"`
     Tags        string         `json:"tags,omitempty" gorm:"type:varchar(255)"`
     VendorID    int            `json:"vendor_id,omitempty" gorm:"index"`
@@ -44,7 +44,7 @@ type Model struct {
     Status      int            `json:"status" gorm:"default:1"`
     CreatedTime int64          `json:"created_time" gorm:"bigint"`
     UpdatedTime int64          `json:"updated_time" gorm:"bigint"`
-    DeletedAt   gorm.DeletedAt `json:"-" gorm:"index"`
+    DeletedAt   gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_model_name,priority:2"`
 
     BoundChannels []BoundChannel `json:"bound_channels,omitempty" gorm:"-"`
     EnableGroups []string       `json:"enable_groups,omitempty" gorm:"-"`

+ 2 - 2
model/vendor_meta.go

@@ -14,13 +14,13 @@ import (
 
 type Vendor struct {
     Id          int            `json:"id"`
-    Name        string         `json:"name" gorm:"size:128;not null;uniqueIndex:uk_vendor_name,where:deleted_at IS NULL"`
+    Name        string         `json:"name" gorm:"size:128;not null;uniqueIndex:uk_vendor_name,priority:1"`
     Description string         `json:"description,omitempty" gorm:"type:text"`
     Icon        string         `json:"icon,omitempty" gorm:"type:varchar(128)"`
     Status      int            `json:"status" gorm:"default:1"`
     CreatedTime int64          `json:"created_time" gorm:"bigint"`
     UpdatedTime int64          `json:"updated_time" gorm:"bigint"`
-    DeletedAt   gorm.DeletedAt `json:"-" gorm:"index"`
+    DeletedAt   gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_vendor_name,priority:2"`
 }
 
 // Insert 创建新的供应商记录