From 600963158e44aa5b73419ad8be19fa414941a150 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Thu, 21 May 2026 12:06:59 +0000 Subject: [PATCH] =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0519_0021_portfolio_tags_data_migration.py | 20 ++++++++++++------- ...520_0022_service_token_unique_index_fix.py | 13 +++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/migrations/versions/20260519_0021_portfolio_tags_data_migration.py b/migrations/versions/20260519_0021_portfolio_tags_data_migration.py index 0208417..3b52404 100644 --- a/migrations/versions/20260519_0021_portfolio_tags_data_migration.py +++ b/migrations/versions/20260519_0021_portfolio_tags_data_migration.py @@ -10,6 +10,7 @@ before running this migration in a live environment. from __future__ import annotations +import os import sqlalchemy as sa from alembic import op from sqlalchemy import inspect, text @@ -20,7 +21,8 @@ branch_labels = None depends_on = None # 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: @@ -28,16 +30,20 @@ def _table_exists(inspector: sa.engine.Inspector, table_name: str) -> bool: 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( - 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}, ) - count = result.scalar() - if not count: + user_id = result.scalar() + if not user_id: raise ValueError( 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 @@ -49,7 +55,7 @@ def run_migration(connection, admin_user_id: str) -> None: GROUP BY LOWER(tag_value) ON CONFLICT (name) DO NOTHING """), - {"admin_id": admin_user_id}, + {"admin_id": user_id}, ) # Fill portfolios.tag_ids from the migrated tags diff --git a/migrations/versions/20260520_0022_service_token_unique_index_fix.py b/migrations/versions/20260520_0022_service_token_unique_index_fix.py index e6b4a17..ade0d55 100644 --- a/migrations/versions/20260520_0022_service_token_unique_index_fix.py +++ b/migrations/versions/20260520_0022_service_token_unique_index_fix.py @@ -25,14 +25,21 @@ def upgrade() -> None: bind = op.get_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"): - bind.execute( + autocommit_bind.execute( __import__("sqlalchemy").text( "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( __import__("sqlalchemy").text( """