const mysql = require('mysql2/promise'); require('dotenv').config(); async function main() { console.log('Comparing database structures between "igcrm" (local) and "crmlive1" (live)...'); // Parse credentials from local DATABASE_URL or default to root@127.0.0.1:3306 let connectionConfig = { host: '127.0.0.1', port: 3306, user: 'root', password: '', }; const dbUrl = process.env.DATABASE_URL; if (dbUrl) { try { const url = dbUrl.replace('mysql://', ''); const [auth, hostPortDb] = url.split('@'); const [user, password] = auth.split(':'); const [hostPort, database] = hostPortDb.split('/'); const [host, port] = hostPort.split(':'); connectionConfig.user = user; connectionConfig.password = password || ''; connectionConfig.host = host; connectionConfig.port = parseInt(port) || 3306; } catch (e) { console.log('Error parsing DATABASE_URL, using defaults.'); } } console.log(`Connecting to MySQL on ${connectionConfig.host}:${connectionConfig.port}...`); try { const connection = await mysql.createConnection(connectionConfig); console.log('Connected successfully!'); // Check if databases exist const [dbs] = await connection.query('SHOW DATABASES'); const dbNames = dbs.map(d => d.Database); const localDb = 'igcrm'; const liveDb = 'crmlive1'; if (!dbNames.includes(localDb)) { console.error(`Local database "${localDb}" not found! Available databases:`, dbNames); await connection.end(); return; } if (!dbNames.includes(liveDb)) { console.error(`Live database "${liveDb}" not found! Available databases:`, dbNames); await connection.end(); return; } console.log(`Querying information_schema for ${localDb} and ${liveDb}...`); // Fetch column definitions for local const [localCols] = await connection.query(` SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY FROM information_schema.columns WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME, ORDINAL_POSITION `, [localDb]); // Fetch column definitions for live const [liveCols] = await connection.query(` SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY FROM information_schema.columns WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME, ORDINAL_POSITION `, [liveDb]); await connection.end(); // Map columns to structured objects const localSchema = {}; localCols.forEach(col => { if (!localSchema[col.TABLE_NAME]) localSchema[col.TABLE_NAME] = {}; localSchema[col.TABLE_NAME][col.COLUMN_NAME] = col; }); const liveSchema = {}; liveCols.forEach(col => { if (!liveSchema[col.TABLE_NAME]) liveSchema[col.TABLE_NAME] = {}; liveSchema[col.TABLE_NAME][col.COLUMN_NAME] = col; }); console.log('\n--- SCHEMA COMPARISON RESULTS ---\n'); // 1. Check for missing tables const localTables = Object.keys(localSchema); const liveTables = Object.keys(liveSchema); const missingInLive = localTables.filter(t => !liveTables.includes(t)); const extraInLive = liveTables.filter(t => !localTables.includes(t)); if (missingInLive.length > 0) { console.log(`❌ Tables in Local (${localDb}) but missing in Live (${liveDb}):`, missingInLive); } if (extraInLive.length > 0) { console.log(`ℹ️ Tables in Live (${liveDb}) but missing in Local (${localDb}) (pre-existing/extra):`, extraInLive); } // 2. Compare shared tables const sharedTables = localTables.filter(t => liveTables.includes(t)); let differencesFound = false; sharedTables.forEach(tableName => { const localTab = localSchema[tableName]; const liveTab = liveSchema[tableName]; const localColsList = Object.keys(localTab); const liveColsList = Object.keys(liveTab); const missingColsInLive = localColsList.filter(c => !liveColsList.includes(c)); const extraColsInLive = liveColsList.filter(c => !localColsList.includes(c)); let tableDiffHeaderPrinted = false; const printTableDiffHeader = () => { if (!tableDiffHeaderPrinted) { console.log(`\nTable: "${tableName}"`); tableDiffHeaderPrinted = true; differencesFound = true; } }; if (missingColsInLive.length > 0) { printTableDiffHeader(); console.log(` ❌ Columns in Local but MISSING in Live:`, missingColsInLive); } if (extraColsInLive.length > 0) { printTableDiffHeader(); console.log(` ℹ️ Columns in Live but MISSING in Local:`, extraColsInLive); } // Compare details of shared columns const sharedCols = localColsList.filter(c => liveColsList.includes(c)); sharedCols.forEach(colName => { const localCol = localTab[colName]; const liveCol = liveTab[colName]; const typeDiff = localCol.COLUMN_TYPE !== liveCol.COLUMN_TYPE; const nullDiff = localCol.IS_NULLABLE !== liveCol.IS_NULLABLE; const defaultDiff = localCol.COLUMN_DEFAULT !== liveCol.COLUMN_DEFAULT; const keyDiff = localCol.COLUMN_KEY !== liveCol.COLUMN_KEY; if (typeDiff || nullDiff || defaultDiff || keyDiff) { printTableDiffHeader(); console.log(` Column: "${colName}" has differences:`); if (typeDiff) console.log(` - Type: Local="${localCol.COLUMN_TYPE}", Live="${liveCol.COLUMN_TYPE}"`); if (nullDiff) console.log(` - Nullable: Local="${localCol.IS_NULLABLE}", Live="${liveCol.IS_NULLABLE}"`); if (defaultDiff) console.log(` - Default: Local="${localCol.COLUMN_DEFAULT}", Live="${liveCol.COLUMN_DEFAULT}"`); if (keyDiff) console.log(` - Key: Local="${localCol.COLUMN_KEY}", Live="${liveCol.COLUMN_KEY}"`); } }); }); if (!differencesFound && missingInLive.length === 0 && extraInLive.length === 0) { console.log('✅ Local and Live schemas are identical!'); } } catch (error) { console.error('Comparison failed:', error); } } main();