Ver Fonte

fix: handle soft-deleted records in model creation to avoid unique constraint violation

When creating a model, check for any existing record with the same name (including soft-deleted).
If a soft-deleted record is found, permanently delete it to free up the name before creating the new model.
This fixes the 'duplicate key value violates unique constraint ix_models_name' error when re-creating a previously deleted model.
kinglee há 3 dias atrás
pai
commit
de6c26a58b
1 ficheiros alterados com 28 adições e 1 exclusões
  1. 28 1
      gpustack/routes/models.py

+ 28 - 1
gpustack/routes/models.py

@@ -6,7 +6,7 @@ from fastapi.responses import RedirectResponse, StreamingResponse
 from urllib.parse import urlencode
 from urllib.parse import urlencode
 from gpustack_runtime.detector import ManufacturerEnum
 from gpustack_runtime.detector import ManufacturerEnum
 from sqlalchemy.orm import selectinload
 from sqlalchemy.orm import selectinload
-from sqlmodel import and_, or_
+from sqlmodel import select, and_, or_
 from sqlmodel.ext.asyncio.session import AsyncSession
 from sqlmodel.ext.asyncio.session import AsyncSession
 from enum import Enum
 from enum import Enum
 
 
@@ -423,6 +423,33 @@ async def create_model(
     # Model & ModelRoute names are unique within their Org. Two Orgs
     # Model & ModelRoute names are unique within their Org. Two Orgs
     # can each have a "llama3" without colliding.
     # can each have a "llama3" without colliding.
     org_scope = ctx.current_principal_id
     org_scope = ctx.current_principal_id
+
+    # Check for ANY existing record with the same name (including soft-deleted)
+    # to avoid unique constraint violations
+    statement = select(Model).where(Model.name == model_in.name)
+    result = await session.exec(statement)
+    any_existing = result.first()
+
+    if any_existing:
+        if any_existing.deleted_at is not None:
+            # Soft-deleted record found - permanently delete it to free up the name
+            await session.delete(any_existing)
+            await session.flush()
+        else:
+            # Active record found - check if it's in the same org scope
+            if any_existing.owner_principal_id == org_scope:
+                raise AlreadyExistsException(
+                    message=f"Model '{model_in.name}' already exists. "
+                    "Please choose a different name or check the existing model."
+                )
+            else:
+                # Different org - still a conflict due to unique constraint on name
+                raise AlreadyExistsException(
+                    message=f"Model name '{model_in.name}' is already in use by another organization. "
+                    "Please choose a different name."
+                )
+
+    # Double-check for the specific org scope (defensive programming)
     existing = await Model.one_by_fields(
     existing = await Model.one_by_fields(
         session,
         session,
         {"name": model_in.name, "owner_principal_id": org_scope},
         {"name": model_in.name, "owner_principal_id": org_scope},