Files
Church-Music/legacy-site/backend/fix_database_schema.py

202 lines
9.1 KiB
Python

#!/usr/bin/env python3
"""
Database Schema Fix Script
Fixes schema mismatches and adds missing indexes/constraints
"""
from postgresql_models import engine
from sqlalchemy import text, inspect
import sys
def run_migration():
"""Run all database schema fixes"""
print("=" * 60)
print("DATABASE SCHEMA FIX SCRIPT")
print("=" * 60)
with engine.begin() as conn:
inspector = inspect(engine)
# ===== FIX 1: Add missing indexes on songs table =====
print("\n📊 Adding indexes on songs table...")
songs_indexes = {idx['name']: idx for idx in inspector.get_indexes('songs')}
if 'idx_song_title' not in songs_indexes:
print(" Creating idx_song_title...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_title ON songs(title)"))
print(" ✅ idx_song_title created")
else:
print(" ✅ idx_song_title already exists")
if 'idx_song_artist' not in songs_indexes:
print(" Creating idx_song_artist...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_artist ON songs(artist)"))
print(" ✅ idx_song_artist created")
else:
print(" ✅ idx_song_artist already exists")
if 'idx_song_band' not in songs_indexes:
print(" Creating idx_song_band...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_song_band ON songs(band)"))
print(" ✅ idx_song_band created")
else:
print(" ✅ idx_song_band already exists")
# ===== FIX 2: Add missing indexes on plans table =====
print("\n📊 Adding indexes on plans table...")
plans_indexes = {idx['name']: idx for idx in inspector.get_indexes('plans')}
if 'idx_plan_date' not in plans_indexes:
print(" Creating idx_plan_date...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_date ON plans(date)"))
print(" ✅ idx_plan_date created")
else:
print(" ✅ idx_plan_date already exists")
if 'idx_plan_profile' not in plans_indexes:
print(" Creating idx_plan_profile...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_profile ON plans(profile_id)"))
print(" ✅ idx_plan_profile created")
else:
print(" ✅ idx_plan_profile already exists")
# ===== FIX 3: Add missing index on profiles table =====
print("\n📊 Adding indexes on profiles table...")
profile_indexes = {idx['name']: idx for idx in inspector.get_indexes('profiles')}
if 'idx_profile_name' not in profile_indexes:
print(" Creating idx_profile_name...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_profile_name ON profiles(name)"))
print(" ✅ idx_profile_name created")
else:
print(" ✅ idx_profile_name already exists")
# ===== FIX 4: Fix plans.date to NOT NULL =====
print("\n🔧 Fixing plans.date constraint...")
plans_cols = {col['name']: col for col in inspector.get_columns('plans')}
if plans_cols['date']['nullable']:
print(" Setting default value for NULL dates...")
conn.execute(text("UPDATE plans SET date = '2025-01-01' WHERE date IS NULL"))
print(" Setting plans.date to NOT NULL...")
conn.execute(text("ALTER TABLE plans ALTER COLUMN date SET NOT NULL"))
print(" ✅ plans.date is now NOT NULL")
else:
print(" ✅ plans.date is already NOT NULL")
# ===== FIX 5: Fix profiles.name to NOT NULL =====
print("\n🔧 Fixing profiles.name constraint...")
profiles_cols = {col['name']: col for col in inspector.get_columns('profiles')}
if profiles_cols['name']['nullable']:
print(" Setting default value for NULL names...")
conn.execute(text("UPDATE profiles SET name = 'Unnamed' WHERE name IS NULL OR name = ''"))
print(" Setting profiles.name to NOT NULL...")
conn.execute(text("ALTER TABLE profiles ALTER COLUMN name SET NOT NULL"))
print(" ✅ profiles.name is now NOT NULL")
else:
print(" ✅ profiles.name is already NOT NULL")
# ===== FIX 6: Add unique constraint on plan_songs =====
print("\n🔧 Adding unique constraint on plan_songs...")
plan_songs_constraints = inspector.get_unique_constraints('plan_songs')
constraint_exists = any('plan_id' in str(c.get('column_names', [])) and 'song_id' in str(c.get('column_names', []))
for c in plan_songs_constraints)
if not constraint_exists:
print(" Creating unique constraint uq_plan_song...")
conn.execute(text("""
ALTER TABLE plan_songs
ADD CONSTRAINT uq_plan_song
UNIQUE (plan_id, song_id)
"""))
print(" ✅ uq_plan_song constraint created")
else:
print(" ✅ unique constraint already exists")
# ===== FIX 7: Fix plan_songs.id to INTEGER (requires recreation) =====
print("\n🔧 Checking plan_songs.id type...")
plan_songs_cols = {col['name']: col for col in inspector.get_columns('plan_songs')}
if plan_songs_cols['id']['type'].__class__.__name__ != 'INTEGER':
print(" ⚠️ plan_songs.id is VARCHAR, needs to be INTEGER AUTOINCREMENT")
print(" Checking if table has data...")
result = conn.execute(text("SELECT COUNT(*) FROM plan_songs"))
count = result.scalar()
if count > 0:
print(f" ⚠️ Table has {count} rows - Manual migration required!")
print(" Skipping this fix to preserve data.")
print(" Note: This will be fixed on next table recreation.")
else:
print(" Table is empty, recreating with correct schema...")
conn.execute(text("DROP TABLE plan_songs"))
conn.execute(text("""
CREATE TABLE plan_songs (
id SERIAL PRIMARY KEY,
plan_id VARCHAR(255),
song_id VARCHAR(255),
order_index INTEGER DEFAULT 0,
CONSTRAINT fk_plan_songs_plan FOREIGN KEY (plan_id)
REFERENCES plans(id) ON DELETE CASCADE,
CONSTRAINT fk_plan_songs_song FOREIGN KEY (song_id)
REFERENCES songs(id) ON DELETE CASCADE,
CONSTRAINT uq_plan_song UNIQUE (plan_id, song_id)
)
"""))
conn.execute(text("CREATE INDEX idx_plan_songs_plan ON plan_songs(plan_id)"))
conn.execute(text("CREATE INDEX idx_plan_songs_order ON plan_songs(plan_id, order_index)"))
print(" ✅ plan_songs table recreated with INTEGER id")
else:
print(" ✅ plan_songs.id is already INTEGER")
# ===== FIX 8: Add missing index on plan_songs order =====
print("\n📊 Adding order index on plan_songs...")
plan_songs_indexes = {idx['name']: idx for idx in inspector.get_indexes('plan_songs')}
if 'idx_plan_songs_order' not in plan_songs_indexes:
print(" Creating idx_plan_songs_order...")
conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plan_songs_order ON plan_songs(plan_id, order_index)"))
print(" ✅ idx_plan_songs_order created")
else:
print(" ✅ idx_plan_songs_order already exists")
# ===== FIX 9: Verify all foreign keys have proper constraints =====
print("\n🔧 Verifying foreign key constraints...")
# Check profile_songs
profile_songs_fks = inspector.get_foreign_keys('profile_songs')
print(f" profile_songs has {len(profile_songs_fks)} foreign keys")
# Check profile_song_keys
profile_song_keys_fks = inspector.get_foreign_keys('profile_song_keys')
print(f" profile_song_keys has {len(profile_song_keys_fks)} foreign keys")
print(" ✅ Foreign key constraints verified")
print("\n" + "=" * 60)
print("✅ DATABASE SCHEMA FIX COMPLETE!")
print("=" * 60)
print("\nSummary of changes:")
print(" • Added indexes on songs (title, artist, band)")
print(" • Added indexes on plans (date, profile_id)")
print(" • Added index on profiles (name)")
print(" • Fixed plans.date to NOT NULL")
print(" • Fixed profiles.name to NOT NULL")
print(" • Added unique constraint on plan_songs")
print(" • Added order index on plan_songs")
print(" • Verified foreign key constraints")
print("\n")
if __name__ == '__main__':
try:
run_migration()
sys.exit(0)
except Exception as e:
print(f"\n❌ ERROR: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
sys.exit(1)