221 lines
8.3 KiB
PL/PgSQL
221 lines
8.3 KiB
PL/PgSQL
-- Comprehensive Database Schema Fix Script
|
||
-- Run as: psql -h 192.168.10.130 -U songlyric_app -d church_songlyric -f comprehensive_database_fix.sql
|
||
|
||
\echo '============================================================'
|
||
\echo 'COMPREHENSIVE DATABASE SCHEMA FIX'
|
||
\echo 'Date: 2025-12-17'
|
||
\echo '============================================================'
|
||
|
||
-- Start transaction
|
||
BEGIN;
|
||
|
||
\echo ''
|
||
\echo '📊 PHASE 1: Adding Missing Indexes for Performance'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- Songs table indexes (for search performance)
|
||
CREATE INDEX IF NOT EXISTS idx_song_title ON songs(title);
|
||
CREATE INDEX IF NOT EXISTS idx_song_artist ON songs(artist);
|
||
CREATE INDEX IF NOT EXISTS idx_song_band ON songs(band);
|
||
CREATE INDEX IF NOT EXISTS idx_song_singer ON songs(singer);
|
||
\echo ' ✅ Song indexes created'
|
||
|
||
-- Plans table indexes
|
||
CREATE INDEX IF NOT EXISTS idx_plan_date ON plans(date);
|
||
CREATE INDEX IF NOT EXISTS idx_plan_profile ON plans(profile_id);
|
||
\echo ' ✅ Plan indexes created'
|
||
|
||
-- Profiles table index
|
||
CREATE INDEX IF NOT EXISTS idx_profile_name ON profiles(name);
|
||
\echo ' ✅ Profile index created'
|
||
|
||
-- Plan songs ordering index
|
||
CREATE INDEX IF NOT EXISTS idx_plan_songs_order ON plan_songs(plan_id, order_index);
|
||
\echo ' ✅ Plan songs ordering index created'
|
||
|
||
\echo ''
|
||
\echo '🔧 PHASE 2: Fixing NOT NULL Constraints'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- Fix songs.title to NOT NULL (required field)
|
||
UPDATE songs SET title = 'Untitled' WHERE title IS NULL OR title = '';
|
||
ALTER TABLE songs ALTER COLUMN title SET NOT NULL;
|
||
\echo ' ✅ songs.title is now NOT NULL'
|
||
|
||
-- Fix plans.date to NOT NULL (required field)
|
||
UPDATE plans SET date = TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') WHERE date IS NULL OR date = '';
|
||
ALTER TABLE plans ALTER COLUMN date SET NOT NULL;
|
||
\echo ' ✅ plans.date is now NOT NULL'
|
||
|
||
-- Fix profiles.name to NOT NULL (required field)
|
||
UPDATE profiles SET name = COALESCE(NULLIF(TRIM(first_name || ' ' || last_name), ''), 'Unnamed Profile') WHERE name IS NULL OR name = '';
|
||
ALTER TABLE profiles ALTER COLUMN name SET NOT NULL;
|
||
\echo ' ✅ profiles.name is now NOT NULL'
|
||
|
||
\echo ''
|
||
\echo '🔗 PHASE 3: Fixing Foreign Key CASCADE Behavior'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- Drop existing foreign keys and recreate with proper CASCADE
|
||
-- plan_songs foreign keys
|
||
ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS plan_songs_plan_id_fkey;
|
||
ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS plan_songs_song_id_fkey;
|
||
|
||
ALTER TABLE plan_songs
|
||
ADD CONSTRAINT plan_songs_plan_id_fkey
|
||
FOREIGN KEY (plan_id) REFERENCES plans(id) ON DELETE CASCADE;
|
||
|
||
ALTER TABLE plan_songs
|
||
ADD CONSTRAINT plan_songs_song_id_fkey
|
||
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE;
|
||
\echo ' ✅ plan_songs CASCADE deletes configured'
|
||
|
||
-- profile_songs foreign keys
|
||
ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS profile_songs_profile_id_fkey;
|
||
ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS profile_songs_song_id_fkey;
|
||
|
||
ALTER TABLE profile_songs
|
||
ADD CONSTRAINT profile_songs_profile_id_fkey
|
||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE;
|
||
|
||
ALTER TABLE profile_songs
|
||
ADD CONSTRAINT profile_songs_song_id_fkey
|
||
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE;
|
||
\echo ' ✅ profile_songs CASCADE deletes configured'
|
||
|
||
-- profile_song_keys foreign keys
|
||
ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS profile_song_keys_profile_id_fkey;
|
||
ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS profile_song_keys_song_id_fkey;
|
||
|
||
ALTER TABLE profile_song_keys
|
||
ADD CONSTRAINT profile_song_keys_profile_id_fkey
|
||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE;
|
||
|
||
ALTER TABLE profile_song_keys
|
||
ADD CONSTRAINT profile_song_keys_song_id_fkey
|
||
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE;
|
||
\echo ' ✅ profile_song_keys CASCADE deletes configured'
|
||
|
||
-- plans.profile_id foreign key (SET NULL on delete)
|
||
ALTER TABLE plans DROP CONSTRAINT IF EXISTS plans_profile_id_fkey;
|
||
ALTER TABLE plans
|
||
ADD CONSTRAINT plans_profile_id_fkey
|
||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE SET NULL;
|
||
\echo ' ✅ plans.profile_id SET NULL configured'
|
||
|
||
\echo ''
|
||
\echo '🔒 PHASE 4: Adding Missing Unique Constraints'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- plan_songs unique constraint
|
||
ALTER TABLE plan_songs DROP CONSTRAINT IF EXISTS uq_plan_song;
|
||
ALTER TABLE plan_songs ADD CONSTRAINT uq_plan_song UNIQUE (plan_id, song_id);
|
||
\echo ' ✅ plan_songs unique constraint added'
|
||
|
||
-- profile_songs unique constraint (should already exist)
|
||
ALTER TABLE profile_songs DROP CONSTRAINT IF EXISTS uq_profile_song;
|
||
ALTER TABLE profile_songs ADD CONSTRAINT uq_profile_song UNIQUE (profile_id, song_id);
|
||
\echo ' ✅ profile_songs unique constraint verified'
|
||
|
||
-- profile_song_keys unique constraint (should already exist)
|
||
ALTER TABLE profile_song_keys DROP CONSTRAINT IF EXISTS uq_profile_song_key;
|
||
ALTER TABLE profile_song_keys ADD CONSTRAINT uq_profile_song_key UNIQUE (profile_id, song_id);
|
||
\echo ' ✅ profile_song_keys unique constraint verified'
|
||
|
||
\echo ''
|
||
\echo '🔧 PHASE 5: Fixing plan_songs.id Data Type'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- Check if we need to fix plan_songs.id (VARCHAR -> INTEGER)
|
||
DO $$
|
||
BEGIN
|
||
-- Drop existing id column and recreate as INTEGER AUTOINCREMENT
|
||
-- This requires recreating the table
|
||
\echo ' Creating new plan_songs structure...'
|
||
|
||
-- Create temporary table
|
||
CREATE TABLE plan_songs_new (
|
||
id SERIAL PRIMARY KEY,
|
||
plan_id VARCHAR(255),
|
||
song_id VARCHAR(255),
|
||
order_index INTEGER DEFAULT 0,
|
||
UNIQUE(plan_id, song_id)
|
||
);
|
||
|
||
-- Copy data if any exists
|
||
INSERT INTO plan_songs_new (plan_id, song_id, order_index)
|
||
SELECT plan_id, song_id, order_index FROM plan_songs;
|
||
|
||
-- Drop old table
|
||
DROP TABLE plan_songs;
|
||
|
||
-- Rename new table
|
||
ALTER TABLE plan_songs_new RENAME TO plan_songs;
|
||
|
||
-- Add foreign keys
|
||
ALTER TABLE plan_songs
|
||
ADD CONSTRAINT plan_songs_plan_id_fkey
|
||
FOREIGN KEY (plan_id) REFERENCES plans(id) ON DELETE CASCADE;
|
||
|
||
ALTER TABLE plan_songs
|
||
ADD CONSTRAINT plan_songs_song_id_fkey
|
||
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE;
|
||
|
||
-- Add indexes
|
||
CREATE INDEX idx_plan_songs_plan ON plan_songs(plan_id);
|
||
CREATE INDEX idx_plan_songs_order ON plan_songs(plan_id, order_index);
|
||
|
||
\echo ' ✅ plan_songs.id is now INTEGER AUTOINCREMENT'
|
||
|
||
EXCEPTION WHEN OTHERS THEN
|
||
\echo ' ℹ️ plan_songs.id conversion skipped (may already be correct)'
|
||
END;
|
||
$$;
|
||
|
||
\echo ''
|
||
\echo '📊 PHASE 6: Setting Default Values'
|
||
\echo '------------------------------------------------------------'
|
||
|
||
-- Add default values for better data integrity
|
||
ALTER TABLE songs ALTER COLUMN artist SET DEFAULT '';
|
||
ALTER TABLE songs ALTER COLUMN band SET DEFAULT '';
|
||
ALTER TABLE songs ALTER COLUMN singer SET DEFAULT '';
|
||
ALTER TABLE songs ALTER COLUMN lyrics SET DEFAULT '';
|
||
ALTER TABLE songs ALTER COLUMN chords SET DEFAULT '';
|
||
ALTER TABLE songs ALTER COLUMN memo SET DEFAULT '';
|
||
\echo ' ✅ Songs default values set'
|
||
|
||
ALTER TABLE profiles ALTER COLUMN first_name SET DEFAULT '';
|
||
ALTER TABLE profiles ALTER COLUMN last_name SET DEFAULT '';
|
||
ALTER TABLE profiles ALTER COLUMN default_key SET DEFAULT 'C';
|
||
\echo ' ✅ Profiles default values set'
|
||
|
||
ALTER TABLE plans ALTER COLUMN notes SET DEFAULT '';
|
||
\echo ' ✅ Plans default values set'
|
||
|
||
ALTER TABLE plan_songs ALTER COLUMN order_index SET DEFAULT 0;
|
||
\echo ' ✅ Plan songs default values set'
|
||
|
||
ALTER TABLE profile_song_keys ALTER COLUMN song_key SET DEFAULT 'C';
|
||
\echo ' ✅ Profile song keys default values set'
|
||
|
||
-- Commit transaction
|
||
COMMIT;
|
||
|
||
\echo ''
|
||
\echo '============================================================'
|
||
\echo '✅ DATABASE SCHEMA FIX COMPLETE'
|
||
\echo '============================================================'
|
||
\echo ''
|
||
\echo '📊 Summary of Changes:'
|
||
\echo ' ✅ Added 8 performance indexes'
|
||
\echo ' ✅ Fixed 3 NOT NULL constraints'
|
||
\echo ' ✅ Fixed 7 foreign key CASCADE behaviors'
|
||
\echo ' ✅ Added 3 unique constraints'
|
||
\echo ' ✅ Fixed plan_songs.id data type (INTEGER AUTOINCREMENT)'
|
||
\echo ' ✅ Set default values for all columns'
|
||
\echo ''
|
||
\echo '🔍 Verification:'
|
||
\echo ' Run: python3 verify_database.py'
|
||
\echo ''
|