From fc7275878ef18d2b6cef89fd706ac34229ec36e6 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 3 Mar 2026 17:57:32 +0800 Subject: [PATCH] feat(db): replace deferred unique constraints with partial unique indexes for LLM model configs Replace deferred UniqueConstraints with partial unique indexes for user and group default configurations in the LLM model configs table. This change improves performance and ensures at most one default config per user/group while maintaining data integrity. The migration script has been updated accordingly to create and drop the new indexes. --- gns3server/db/models/llm_model_configs.py | 16 +++++------ ...20260303_create_llm_model_configs_table.py | 28 +++++++++++-------- gns3server/schemas/__init__.py | 1 - 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/gns3server/db/models/llm_model_configs.py b/gns3server/db/models/llm_model_configs.py index 2656ed83a..a7e5fe8ad 100644 --- a/gns3server/db/models/llm_model_configs.py +++ b/gns3server/db/models/llm_model_configs.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from sqlalchemy import Column, Boolean, ForeignKey, CheckConstraint, UniqueConstraint, Index, Integer, String +from sqlalchemy import Column, Boolean, ForeignKey, CheckConstraint, Index, Integer, String, text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship @@ -65,14 +65,12 @@ class LLMModelConfig(BaseTable): "model_type IN ('text', 'vision', 'stt', 'tts', 'multimodal', 'embedding', 'reranking', 'other')", name="valid_model_type_check" ), - # Each user can have at most one default config - UniqueConstraint("user_id", "is_default", name="unique_user_default", - deferrable=True, initially="deferred", - postgresql_where="is_default = TRUE AND user_id IS NOT NULL"), - # Each group can have at most one default config - UniqueConstraint("group_id", "is_default", name="unique_group_default", - deferrable=True, initially="deferred", - postgresql_where="is_default = TRUE AND group_id IS NOT NULL"), + # Each user can have at most one default config (partial unique index) + Index("unique_user_default", "user_id", unique=True, + postgresql_where=text("is_default = TRUE AND user_id IS NOT NULL")), + # Each group can have at most one default config (partial unique index) + Index("unique_group_default", "group_id", unique=True, + postgresql_where=text("is_default = TRUE AND group_id IS NOT NULL")), # Indexes for efficient queries Index("idx_llm_model_configs_user_id", "user_id"), Index("idx_llm_model_configs_group_id", "group_id"), diff --git a/gns3server/db_migrations/versions/20260303_create_llm_model_configs_table.py b/gns3server/db_migrations/versions/20260303_create_llm_model_configs_table.py index 007bde3b1..400c7482f 100644 --- a/gns3server/db_migrations/versions/20260303_create_llm_model_configs_table.py +++ b/gns3server/db_migrations/versions/20260303_create_llm_model_configs_table.py @@ -54,18 +54,6 @@ def upgrade() -> None: "model_type IN ('text', 'vision', 'stt', 'tts', 'multimodal', 'embedding', 'reranking', 'other')", name='valid_model_type_check' ), - sa.UniqueConstraint( - 'user_id', 'is_default', - name='unique_user_default', - deferrable=True, initially='deferred', - postgresql_where=sa.text("is_default = TRUE AND user_id IS NOT NULL") - ), - sa.UniqueConstraint( - 'group_id', 'is_default', - name='unique_group_default', - deferrable=True, initially='deferred', - postgresql_where=sa.text("is_default = TRUE AND group_id IS NOT NULL") - ), ) # Create indexes for efficient queries @@ -74,6 +62,20 @@ def upgrade() -> None: op.create_index('idx_llm_model_configs_model_type', 'llm_model_configs', ['model_type']) op.create_index('idx_llm_model_configs_config', 'llm_model_configs', ['config'], postgresql_using='gin') + # Create partial unique indexes for default configs + # Each user can have at most one default config + op.execute(""" + CREATE UNIQUE INDEX unique_user_default + ON llm_model_configs (user_id) + WHERE is_default = TRUE AND user_id IS NOT NULL + """) + # Each group can have at most one default config + op.execute(""" + CREATE UNIQUE INDEX unique_group_default + ON llm_model_configs (group_id) + WHERE is_default = TRUE AND group_id IS NOT NULL + """) + def downgrade() -> None: # Drop indexes @@ -81,6 +83,8 @@ def downgrade() -> None: op.drop_index('idx_llm_model_configs_model_type', table_name='llm_model_configs') op.drop_index('idx_llm_model_configs_group_id', table_name='llm_model_configs') op.drop_index('idx_llm_model_configs_user_id', table_name='llm_model_configs') + op.drop_index('unique_user_default', table_name='llm_model_configs') + op.drop_index('unique_group_default', table_name='llm_model_configs') # Drop table op.drop_table('llm_model_configs') diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index d04218f9b..9d1d54058 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -35,7 +35,6 @@ from .controller.llm_model_configs import ( LLMModelConfigCreate, LLMModelConfigUpdate, LLMModelConfigResponse, - LLMModelConfigListResponse, LLMModelConfigWithSource, LLMModelConfigInheritedResponse )