Files
mtgonline/backend/MIGRATION_AUDIT_REPORT.md
T

14 KiB
Raw Blame History

Database Migration Audit Report

Project: mtgonline backend
Date: 2026-07-23
Migrations Reviewed: 5 files in alembic/versions/


Migration Chain

# File Revision Down Revision Tables Created
0 000_base_tables.py 000 None mtgonline_users, mtgonline_decklist_files, mtgonline_rooms
1 001_initial_user_schema.py 001 000 15 user data tables + re-creates 3 base tables
2 002_user_deck_building_tables.py 002 001 user_decks, user_deck_cards, deck_precedents, deck_precedent_cards, card_suggestions
3 003_mtgonline_cards_table.py 003 002 mtgonline_cards
4 004_card_import_table.py 004 003 user_card_imports

Migration-by-Migration Status

Migration 000: 000_base_tables.py⚠️ PASS (with warnings)

Status: PASS
Issues: None critical.

Check Result
upgrade() exists
downgrade() exists
mtgonline_users columns correct 20 columns, proper PK, indexes, unique constraints
mtgonline_decklist_files columns correct 11 columns, FK to users, indexes
mtgonline_rooms columns correct 12 columns, FK to users, indexes
Foreign keys valid All reference mtgonline_users.id
Indexes created idx_decklist_files_user, idx_decklist_files_name, idx_rooms_created_by, idx_rooms_name
Unique constraints username, email on users
Downgrade order correct Drops dependent tables first

Migration 001: 001_initial_user_schema.py FAIL (Critical)

Status: FAIL
Critical Issue: Re-creates base tables that migration 000 already created.

Critical Issues

# Issue Severity Details
1 Duplicate table creation 🔴 CRITICAL The upgrade() function re-creates mtgonline_users, mtgonline_decklist_files, and mtgonline_rooms — tables that migration 000 already created. This will cause sqlalchemy.exc.ProgrammingError: relation "mtgonline_users" already exists when running alembic upgrade head.
2 Missing mtgonline_decklist_folders table 🔴 CRITICAL user_decks.folder_id (migration 002) references mtgonline_decklist_folders.id, but this table is never created in any migration. It only exists in the ORM model (models.py). Migration 002 will fail with a FK error.
3 Missing mtgonline_rooms_gametypes table 🟡 WARNING RoomGameType model references mtgonline_rooms_gametypes table, never created in any migration.
4 Missing mtgonline_bans table 🟡 WARNING Ban model references mtgonline_bans table, never created in any migration.
5 Missing mtgonline_log table 🟡 WARNING GameLog model references mtgonline_log table, never created in any migration.
6 Missing mtgonline_audit table 🟡 WARNING AuditLog model references mtgonline_audit table, never created in any migration.

Table Creation Analysis (001 upgrade)

All 15 dependent tables are created correctly with valid foreign keys:

Table FK References Valid?
user_sessions mtgonline_users.id
deck_versions mtgonline_decklist_files.id (if base tables exist)
game_replays mtgonline_rooms.id (if base tables exist)
replay_players game_replays.id, mtgonline_users.id, mtgonline_decklist_files.id
game_outcomes mtgonline_users.id, game_replays.game_uuid
user_statistics mtgonline_users.id (PK)
user_card_collection mtgonline_users.id
card_wishlist mtgonline_users.id
user_groups mtgonline_users.id
group_members user_groups.id, mtgonline_users.id
group_chat_messages user_groups.id, mtgonline_users.id
user_networks mtgonline_users.id
network_members user_networks.id, mtgonline_users.id
user_preferences mtgonline_users.id (PK)
user_activity_log mtgonline_users.id

Downgrade Analysis

Check Result
Drop order correct (reverse dependency order)
Indexes dropped ⚠️ Not explicitly dropped (but op.drop_table() handles this)
Base tables dropped (at end, after dependents)

Comment Numbering Issue

The upgrade() function has inconsistent section numbering:

  • "0. Base Tables" → "0.1. Rooms Table" → "1. User Sessions" → "2. Deck Versions" → "4. Game Replays" (skips 3)

Migration 002: 002_user_deck_building_tables.py FAIL (Critical)

Status: FAIL
Critical Issue: References non-existent mtgonline_decklist_folders table.

Check Result
upgrade() exists
downgrade() exists
user_decks FK to mtgonline_decklist_folders.id Table never created in any migration
user_deck_cards FK to user_decks.id
deck_precedent_cards FK to deck_precedents.id
card_suggestions FK to user_decks.id
Unique constraints uq_deck_card_unique, uq_precedent_card_unique, uq_suggestion_unique
Indexes created
Downgrade order correct

Additional Issues

# Issue Severity
7 user_deck_cards.card_id has no FK constraint in migration, but model defines ForeignKey("mtgonline_cards.id") 🟡 WARNING
8 card_suggestions.source_card_id has no FK constraint in migration 🟡 WARNING
9 deck_precedent_cards.card_id has no FK constraint in migration 🟡 WARNING
10 deck_precedents.created_by has no FK constraint in migration 🟡 WARNING

Migration 003: 003_mtgonline_cards_table.py PASS

Status: PASS

Check Result
upgrade() exists
downgrade() exists
mtgonline_cards columns correct 22 columns
Indexes created idx_mtgonline_cards_name, idx_mtgonline_cards_set
Foreign keys None (standalone table)
Unique constraints None

Migration 004: 004_card_import_table.py⚠️ PASS (with warnings)

Status: PASS
Issues: Minor consistency issues.

Check Result
upgrade() exists
downgrade() exists
user_card_imports columns correct 5 columns
FK to mtgonline_users.id
Unique constraint uq_user_card_imports_user_id
Index idx_user_card_imports_user
Primary key definition ⚠️ sa.Column('id', sa.Integer(), autoincrement=True, nullable=False) + sa.PrimaryKeyConstraint('id') — redundant but functional

Cross-Reference: Models vs Migrations

Tables in Models but NOT in Any Migration (🔴 CRITICAL)

Model Table Name Referenced By
DecklistFolder mtgonline_decklist_folders DecklistFile.folder_id, UserDeck.folder_id
RoomGameType mtgonline_rooms_gametypes Room.game_types
Ban mtgonline_bans Admin ban records
GameLog mtgonline_log Game chat logs
AuditLog mtgonline_audit Admin action audit trail
MtgCardMirror mtg_cards_mirror Card mirror for deckbuilding
DeckCardLink deck_card_links Junction: decks ↔ mirrored cards
CardImportBatch card_import_batches Card import batch tracking
UserCardImportRecord user_card_imports_confirmed Confirmed import records

These 9 tables must be created in migrations before any migration that references them can succeed.

Column Type Mismatches (Model vs Migration)

mtgonline_users

Column Migration Model Match?
username String(50) String(64)
password_hash String(255) String(128)
salt String(32) String(128)
display_name String(100) Not in model ⚠️
avatar_url String(500) Not in model ⚠️
country String(100) String(2)
real_name String(255) String(128)
avatar_bmp LargeBinary() Text
privlevel Integer() String(50)
is_active Boolean() Boolean()
is_banned Boolean() Boolean()
ban_reason Text() Text()
ban_ends DateTime() DateTime()
vip_status Boolean() Integer()
vip_expiry DateTime() DateTime()
creation_date DateTime() DateTime()
last_login DateTime() DateTime()

mtgonline_decklist_files

Column Migration Model Match?
user_id FK column owner_id (different name)
name String(255) String(255)
content Text() Text() (nullable=True) ⚠️
description Text() Not in model ⚠️
format String(50), default='standard' String(50), default='native'
is_favorite Boolean() Not in model ⚠️
import_source String(50) Not in model ⚠️
import_confidence Float() Not in model ⚠️
last_played DateTime() Not in model ⚠️
Missing folder_id
Missing status

mtgonline_rooms

Column Migration Model Match?
name String(100) String(100), unique=True ⚠️ (migration missing unique)
description Text() Text()
max_players Integer(), default=8 Not in model ⚠️
is_public Boolean() Not in model ⚠️
is_password_protected Boolean() Boolean()
password_hash String(255) String(128)
game_type String(50) Not in model ⚠️
format String(50) Not in model ⚠️
created_by Integer() Not in model ⚠️

Summary of All Issues

🔴 Critical (Must Fix Before Deployment)

# Issue Location Impact
1 Migration 001 re-creates base tables 001_initial_user_schema.py:upgrade() alembic upgrade head will FAIL — tables already exist from migration 000
2 mtgonline_decklist_folders never created Missing from all migrations Migration 002 (user_decks.folder_id) will FAIL with FK error
3 9 model tables have no migration mtgonline_decklist_folders, mtgonline_rooms_gametypes, mtgonline_bans, mtgonline_log, mtgonline_audit, mtg_cards_mirror, deck_card_links, card_import_batches, user_card_imports_confirmed Any code referencing these tables will fail at runtime

🟡 Warnings (Should Fix)

# Issue Location Impact
4 Column type mismatches (15+ columns) Migration 000/001 vs models Schema drift — DB won't match ORM definitions
5 Column name mismatch (user_id vs owner_id) mtgonline_decklist_files ORM won't map correctly
6 Missing unique=True on mtgonline_rooms.name Migration 000 Model defines it as unique
7 Missing FK constraints on junction table columns Migration 002 card_id, source_card_id, created_by lack FK references
8 Inconsistent section numbering in migration 001 001_initial_user_schema.py Code readability
9 Redundant PK definition in migration 004 004_card_import_table.py Works but messy

Informational

# Issue Location
10 Two card table models: MtgCardMirror (mtg_cards_mirror) and MtonlineCard (mtgonline_cards) Different tables, different purposes
11 Migration 001 creates user_card_collection without composite unique constraint that model defines Model has UniqueConstraint('user_id', 'card_id', 'is_foil', 'is_alt_art')
12 user_card_collection migration missing composite index idx_collection_user_card Model defines it

Recommendations

Immediate (Blockers)

  1. Remove duplicate table creation from migration 001 — Delete the mtgonline_users, mtgonline_decklist_files, and mtgonline_rooms op.create_table() calls from 001_initial_user_schema.py. These are already created by migration 000.

  2. Create migration for mtgonline_decklist_folders — This table is referenced by both mtgonline_decklist_files.folder_id (model) and user_decks.folder_id (migration 002). Add it before migration 002 runs.

  3. Create migrations for all missing tables — At minimum: mtgonline_rooms_gametypes, mtgonline_bans, mtgonline_log, mtgonline_audit, mtg_cards_mirror, deck_card_links, card_import_batches, user_card_imports_confirmed.

Short-term (Consistency)

  1. Align migration schemas with ORM models — Fix all column type mismatches and missing columns. The migrations should be the source of truth for the database, and models should match.

  2. Add missing FK constraints in migration 002 — Add ForeignKey to card_id, source_card_id, and created_by columns.

  3. Add missing unique constraint on mtgonline_rooms.name — Migration 000 should include unique=True.

  4. Add missing constraints to user_card_collection — Migration 001 should include the composite unique constraint and index that the model defines.

Long-term (Architecture)

  1. Decide on migration strategy — Either:

    • (a) Remove migration 001's duplicate base tables and keep migration 000 as the single source of base table creation, OR
    • (b) Remove migration 000 entirely and let migration 001 handle all base tables (but this is risky for a production database).
  2. Add Alembic environment script — Create alembic/env.py with include_object filter to auto-detect table creation order and prevent circular dependencies.

  3. Consider using op.create_foreign_key() explicitly — Some FK definitions in the migrations use inline ForeignKey() which is fine, but explicit op.create_foreign_key() calls are more readable and Alembic can better track them for downgrade.