Make migrations idempotent for production DB with pre-existing columns
Check for existing columns/tables before adding to avoid duplicate column errors when production DB state is ahead of Alembic history. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,12 +17,22 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# Check which columns already exist to make migration idempotent
|
||||||
# Split into two batch operations to avoid SQLite circular dependency error
|
conn = op.get_bind()
|
||||||
|
columns = [row[1] for row in conn.execute(sa.text("PRAGMA table_info('games')"))]
|
||||||
|
|
||||||
|
cols_to_add = []
|
||||||
|
if 'current_turn_team_id' not in columns:
|
||||||
|
cols_to_add.append(sa.Column('current_turn_team_id', sa.Integer(), nullable=True))
|
||||||
|
if 'turn_order' not in columns:
|
||||||
|
cols_to_add.append(sa.Column('turn_order', sa.JSON(), nullable=True))
|
||||||
|
if 'is_steal_mode' not in columns:
|
||||||
|
cols_to_add.append(sa.Column('is_steal_mode', sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
|
if cols_to_add:
|
||||||
with op.batch_alter_table('games', schema=None) as batch_op:
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('current_turn_team_id', sa.Integer(), nullable=True))
|
for col in cols_to_add:
|
||||||
batch_op.add_column(sa.Column('turn_order', sa.JSON(), nullable=True))
|
batch_op.add_column(col)
|
||||||
batch_op.add_column(sa.Column('is_steal_mode', sa.Boolean(), nullable=True))
|
|
||||||
|
|
||||||
with op.batch_alter_table('games', schema=None) as batch_op:
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
batch_op.create_foreign_key('fk_games_current_turn_team', 'teams', ['current_turn_team_id'], ['id'])
|
batch_op.create_foreign_key('fk_games_current_turn_team', 'teams', ['current_turn_team_id'], ['id'])
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
# Create question_shares table if it doesn't exist
|
||||||
|
tables = [row[0] for row in conn.execute(sa.text("SELECT name FROM sqlite_master WHERE type='table'"))]
|
||||||
|
if 'question_shares' not in tables:
|
||||||
op.create_table('question_shares',
|
op.create_table('question_shares',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('question_id', sa.Integer(), nullable=False),
|
sa.Column('question_id', sa.Integer(), nullable=False),
|
||||||
@@ -30,16 +35,32 @@ def upgrade():
|
|||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('question_id', 'shared_with_user_id', name='unique_question_share')
|
sa.UniqueConstraint('question_id', 'shared_with_user_id', name='unique_question_share')
|
||||||
)
|
)
|
||||||
# Split into separate batch operations to avoid SQLite circular dependency
|
|
||||||
# with the existing current_turn_team_id foreign key
|
|
||||||
with op.batch_alter_table('games', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('completed_at', sa.DateTime(), nullable=True))
|
|
||||||
batch_op.add_column(sa.Column('winners', sa.JSON(), nullable=True))
|
|
||||||
|
|
||||||
with op.batch_alter_table('games', schema=None) as batch_op:
|
# Check existing columns to make migration idempotent
|
||||||
batch_op.drop_column('turn_order')
|
game_columns = [row[1] for row in conn.execute(sa.text("PRAGMA table_info('games')"))]
|
||||||
batch_op.drop_column('is_steal_mode')
|
question_columns = [row[1] for row in conn.execute(sa.text("PRAGMA table_info('questions')"))]
|
||||||
|
|
||||||
|
# Add new game columns if missing
|
||||||
|
game_cols_to_add = []
|
||||||
|
if 'completed_at' not in game_columns:
|
||||||
|
game_cols_to_add.append(sa.Column('completed_at', sa.DateTime(), nullable=True))
|
||||||
|
if 'winners' not in game_columns:
|
||||||
|
game_cols_to_add.append(sa.Column('winners', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
if game_cols_to_add:
|
||||||
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
|
for col in game_cols_to_add:
|
||||||
|
batch_op.add_column(col)
|
||||||
|
|
||||||
|
# Drop old game columns if they still exist
|
||||||
|
game_cols_to_drop = [c for c in ['turn_order', 'is_steal_mode'] if c in game_columns]
|
||||||
|
if game_cols_to_drop:
|
||||||
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
|
for col_name in game_cols_to_drop:
|
||||||
|
batch_op.drop_column(col_name)
|
||||||
|
|
||||||
|
# Add created_by to questions if missing
|
||||||
|
if 'created_by' not in question_columns:
|
||||||
with op.batch_alter_table('questions', schema=None) as batch_op:
|
with op.batch_alter_table('questions', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('created_by', sa.Integer(), nullable=True))
|
batch_op.add_column(sa.Column('created_by', sa.Integer(), nullable=True))
|
||||||
batch_op.create_foreign_key('fk_questions_created_by', 'users', ['created_by'], ['id'])
|
batch_op.create_foreign_key('fk_questions_created_by', 'users', ['created_by'], ['id'])
|
||||||
|
|||||||
Reference in New Issue
Block a user