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

136 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""
Comprehensive Database Schema Fix Script
Applies all necessary schema improvements including indexes, constraints, and foreign keys
"""
from postgresql_models import engine, SessionLocal
from sqlalchemy import text, inspect
import sys
def print_header(text):
print(f"\n{'='*70}")
print(f" {text}")
print(f"{'='*70}\n")
def print_section(text):
print(f"\n{text}")
print("-" * 70)
def execute_sql(conn, sql, description):
"""Execute SQL with error handling"""
try:
conn.execute(text(sql))
print(f"{description}")
return True
except Exception as e:
print(f" ⚠️ {description}: {str(e)[:80]}")
return False
def main():
print_header("COMPREHENSIVE DATABASE SCHEMA FIX")
print(f"Date: 2025-12-17")
db = SessionLocal()
try:
with engine.begin() as conn:
# PHASE 1: Adding Missing Indexes
print_section("📊 PHASE 1: Adding Performance Indexes")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_song_title ON songs(title)", "Song title index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_song_artist ON songs(artist)", "Song artist index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_song_band ON songs(band)", "Song band index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_song_singer ON songs(singer)", "Song singer index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_plan_date ON plans(date)", "Plan date index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_plan_profile ON plans(profile_id)", "Plan profile index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_profile_name ON profiles(name)", "Profile name index")
execute_sql(conn, "CREATE INDEX IF NOT EXISTS idx_plan_songs_order ON plan_songs(plan_id, order_index)", "Plan songs ordering index")
# PHASE 2: Fixing NOT NULL Constraints
print_section("🔧 PHASE 2: Fixing NOT NULL Constraints")
execute_sql(conn, "UPDATE songs SET title = 'Untitled' WHERE title IS NULL OR title = ''", "Clean songs.title")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN title SET NOT NULL", "songs.title NOT NULL")
execute_sql(conn, "UPDATE plans SET date = TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') WHERE date IS NULL OR date = ''", "Clean plans.date")
execute_sql(conn, "ALTER TABLE plans ALTER COLUMN date SET NOT NULL", "plans.date NOT NULL")
execute_sql(conn, "UPDATE profiles SET name = COALESCE(NULLIF(TRIM(first_name || ' ' || last_name), ''), 'Unnamed Profile') WHERE name IS NULL OR name = ''", "Clean profiles.name")
execute_sql(conn, "ALTER TABLE profiles ALTER COLUMN name SET NOT NULL", "profiles.name NOT NULL")
# PHASE 3: Fixing Foreign Key CASCADE Behavior
print_section("🔗 PHASE 3: Fixing Foreign Key CASCADE Behavior")
# plan_songs foreign keys
execute_sql(conn, "ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS plan_songs_plan_id_fkey", "Drop old plan_songs.plan_id FK")
execute_sql(conn, "ALTER TABLE plan_songs ADD CONSTRAINT plan_songs_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES plans(id) ON DELETE CASCADE", "Add plan_songs.plan_id CASCADE")
execute_sql(conn, "ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS plan_songs_song_id_fkey", "Drop old plan_songs.song_id FK")
execute_sql(conn, "ALTER TABLE plan_songs ADD CONSTRAINT plan_songs_song_id_fkey FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE", "Add plan_songs.song_id CASCADE")
# profile_songs foreign keys
execute_sql(conn, "ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS profile_songs_profile_id_fkey", "Drop old profile_songs.profile_id FK")
execute_sql(conn, "ALTER TABLE profile_songs ADD CONSTRAINT profile_songs_profile_id_fkey FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE", "Add profile_songs.profile_id CASCADE")
execute_sql(conn, "ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS profile_songs_song_id_fkey", "Drop old profile_songs.song_id FK")
execute_sql(conn, "ALTER TABLE profile_songs ADD CONSTRAINT profile_songs_song_id_fkey FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE", "Add profile_songs.song_id CASCADE")
# profile_song_keys foreign keys
execute_sql(conn, "ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS profile_song_keys_profile_id_fkey", "Drop old profile_song_keys.profile_id FK")
execute_sql(conn, "ALTER TABLE profile_song_keys ADD CONSTRAINT profile_song_keys_profile_id_fkey FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE", "Add profile_song_keys.profile_id CASCADE")
execute_sql(conn, "ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS profile_song_keys_song_id_fkey", "Drop old profile_song_keys.song_id FK")
execute_sql(conn, "ALTER TABLE profile_song_keys ADD CONSTRAINT profile_song_keys_song_id_fkey FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE", "Add profile_song_keys.song_id CASCADE")
# plans.profile_id foreign key (SET NULL)
execute_sql(conn, "ALTER TABLE plans DROP CONSTRAINT IF EXISTS plans_profile_id_fkey", "Drop old plans.profile_id FK")
execute_sql(conn, "ALTER TABLE plans ADD CONSTRAINT plans_profile_id_fkey FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE SET NULL", "Add plans.profile_id SET NULL")
# PHASE 4: Adding Unique Constraints
print_section("🔒 PHASE 4: Adding Unique Constraints")
execute_sql(conn, "ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS uq_plan_song", "Drop old plan_songs unique constraint")
execute_sql(conn, "ALTER TABLE plan_songs ADD CONSTRAINT uq_plan_song UNIQUE (plan_id, song_id)", "Add plan_songs unique constraint")
execute_sql(conn, "ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS uq_profile_song", "Drop old profile_songs unique constraint")
execute_sql(conn, "ALTER TABLE profile_songs ADD CONSTRAINT uq_profile_song UNIQUE (profile_id, song_id)", "Add profile_songs unique constraint")
execute_sql(conn, "ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS uq_profile_song_key", "Drop old profile_song_keys unique constraint")
execute_sql(conn, "ALTER TABLE profile_song_keys ADD CONSTRAINT uq_profile_song_key UNIQUE (profile_id, song_id)", "Add profile_song_keys unique constraint")
# PHASE 5: Setting Default Values
print_section("📊 PHASE 5: Setting Default Values")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN artist SET DEFAULT ''", "Songs artist default")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN band SET DEFAULT ''", "Songs band default")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN singer SET DEFAULT ''", "Songs singer default")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN lyrics SET DEFAULT ''", "Songs lyrics default")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN chords SET DEFAULT ''", "Songs chords default")
execute_sql(conn, "ALTER TABLE songs ALTER COLUMN memo SET DEFAULT ''", "Songs memo default")
execute_sql(conn, "ALTER TABLE profiles ALTER COLUMN first_name SET DEFAULT ''", "Profiles first_name default")
execute_sql(conn, "ALTER TABLE profiles ALTER COLUMN last_name SET DEFAULT ''", "Profiles last_name default")
execute_sql(conn, "ALTER TABLE profiles ALTER COLUMN default_key SET DEFAULT 'C'", "Profiles default_key default")
execute_sql(conn, "ALTER TABLE plans ALTER COLUMN notes SET DEFAULT ''", "Plans notes default")
execute_sql(conn, "ALTER TABLE plan_songs ALTER COLUMN order_index SET DEFAULT 0", "Plan songs order_index default")
execute_sql(conn, "ALTER TABLE profile_song_keys ALTER COLUMN song_key SET DEFAULT 'C'", "Profile song keys default")
print_header("✅ DATABASE SCHEMA FIX COMPLETE")
print("\n📊 Summary:")
print(" ✅ Added 8 performance indexes")
print(" ✅ Fixed 3 NOT NULL constraints")
print(" ✅ Fixed 7 foreign key CASCADE behaviors")
print(" ✅ Added 3 unique constraints")
print(" ✅ Set default values for all columns")
print("\n🔍 Next Step:")
print(" Run: python3 verify_database.py")
print()
except Exception as e:
print(f"\n❌ ERROR: {str(e)}")
sys.exit(1)
finally:
db.close()
if __name__ == "__main__":
main()