igcrmapi/scratch/compare_schemas.js

169 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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();