마이그레이션 버그 수정

This commit is contained in:
2026-05-21 12:06:59 +00:00
parent 4bf6b554a7
commit 600963158e
2 changed files with 23 additions and 10 deletions
@@ -10,6 +10,7 @@ before running this migration in a live environment.
from __future__ import annotations from __future__ import annotations
import os
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op from alembic import op
from sqlalchemy import inspect, text from sqlalchemy import inspect, text
@@ -20,7 +21,8 @@ branch_labels = None
depends_on = None depends_on = None
# Operator must confirm this user ID exists in the users table before running. # Operator must confirm this user ID exists in the users table before running.
MIGRATION_ADMIN_ID = "migration-admin" # Can be overridden via MIGRATION_ADMIN_ID environment variable.
MIGRATION_ADMIN_ID = os.environ.get("MIGRATION_ADMIN_ID", "migration-admin")
def _table_exists(inspector: sa.engine.Inspector, table_name: str) -> bool: def _table_exists(inspector: sa.engine.Inspector, table_name: str) -> bool:
@@ -28,16 +30,20 @@ def _table_exists(inspector: sa.engine.Inspector, table_name: str) -> bool:
def run_migration(connection, admin_user_id: str) -> None: def run_migration(connection, admin_user_id: str) -> None:
"""Core migration logic — extracted for testability.""" """Core migration logic — extracted for testability.
admin_user_id can be either a UUID (id) or username.
"""
# Try to find user by id (UUID) first, then by username
result = connection.execute( result = connection.execute(
text("SELECT COUNT(*) FROM users WHERE id = :uid"), text("SELECT id FROM users WHERE id = :uid OR username = :uid LIMIT 1"),
{"uid": admin_user_id}, {"uid": admin_user_id},
) )
count = result.scalar() user_id = result.scalar()
if not count: if not user_id:
raise ValueError( raise ValueError(
f"Migration admin user not found in users table: {admin_user_id}. " f"Migration admin user not found in users table: {admin_user_id}. "
"Set MIGRATION_ADMIN_ID to a valid user ID before running." "Set MIGRATION_ADMIN_ID to a valid user ID (UUID) or username before running."
) )
# Migrate distinct (lowercased) tag_values from portfolio_tags → tags master # Migrate distinct (lowercased) tag_values from portfolio_tags → tags master
@@ -49,7 +55,7 @@ def run_migration(connection, admin_user_id: str) -> None:
GROUP BY LOWER(tag_value) GROUP BY LOWER(tag_value)
ON CONFLICT (name) DO NOTHING ON CONFLICT (name) DO NOTHING
"""), """),
{"admin_id": admin_user_id}, {"admin_id": user_id},
) )
# Fill portfolios.tag_ids from the migrated tags # Fill portfolios.tag_ids from the migrated tags
@@ -25,14 +25,21 @@ def upgrade() -> None:
bind = op.get_bind() bind = op.get_bind()
inspector = inspect(bind) inspector = inspect(bind)
# First, commit any pending transaction before changing isolation level
bind.commit()
# Set AUTOCOMMIT mode for CREATE UNIQUE INDEX CONCURRENTLY
autocommit_bind = bind.execution_options(isolation_level="AUTOCOMMIT")
# Drop old index if it exists (non-partial index)
if _index_exists(inspector, "auth_tokens", "idx_auth_tokens_service_name_unique"): if _index_exists(inspector, "auth_tokens", "idx_auth_tokens_service_name_unique"):
bind.execute( autocommit_bind.execute(
__import__("sqlalchemy").text( __import__("sqlalchemy").text(
"DROP INDEX IF EXISTS idx_auth_tokens_service_name_unique" "DROP INDEX IF EXISTS idx_auth_tokens_service_name_unique"
) )
) )
autocommit_bind = bind.execution_options(isolation_level="AUTOCOMMIT") # Create new partial unique index with WHERE clause
autocommit_bind.execute( autocommit_bind.execute(
__import__("sqlalchemy").text( __import__("sqlalchemy").text(
""" """