233 lines
7.8 KiB
JavaScript
233 lines
7.8 KiB
JavaScript
import dotenv from 'dotenv';
|
|
import fs from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { Pool } from 'pg';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// Load environment variables
|
|
dotenv.config({ path: join(__dirname, '..', '.env') });
|
|
|
|
// Database configuration
|
|
const dbConfig = {
|
|
host: process.env.LOCAL_DB_HOST || '192.168.8.151',
|
|
port: parseInt(process.env.LOCAL_DB_PORT || '5432'),
|
|
user: process.env.LOCAL_DB_USER || 'postgres',
|
|
password: process.env.LOCAL_DB_PASSWORD || 'postgres',
|
|
database: process.env.LOCAL_DB_NAME || 'institutional_trader'
|
|
};
|
|
|
|
/**
|
|
* Execute SQL file or query
|
|
*/
|
|
async function executeSQL(query, params = []) {
|
|
const pool = new Pool(dbConfig);
|
|
|
|
try {
|
|
const result = await pool.query(query, params);
|
|
return result.rows;
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run analysis queries to show current state
|
|
*/
|
|
async function analyzeDatabase() {
|
|
console.log('\n📊 Analyzing database date/time formats...\n');
|
|
|
|
const queries = {
|
|
'CreatedDate Analysis': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date,
|
|
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format,
|
|
COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "CreatedDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format,
|
|
COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_dates,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`,
|
|
|
|
'CreatedTime Analysis': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_with_space,
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}[AP]M$') as valid_no_space,
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as valid_minutes_only_space,
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}[AP]M$') as valid_minutes_only_no_space,
|
|
COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~* '(AM|PM)') as no_ampm,
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}\\s*[AP]M$' AND "CreatedTime"::text !~ ':') as missing_colon,
|
|
COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_times,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`,
|
|
|
|
'ExpirationDate Analysis': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date,
|
|
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format,
|
|
COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "ExpirationDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format,
|
|
COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_dates,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`
|
|
};
|
|
|
|
for (const [name, query] of Object.entries(queries)) {
|
|
console.log(`\n${name}:`);
|
|
console.log('─'.repeat(50));
|
|
try {
|
|
const results = await executeSQL(query);
|
|
if (results.length > 0) {
|
|
console.table(results[0]);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error running ${name}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the fix script
|
|
*/
|
|
async function runFix() {
|
|
console.log('\n🔧 Running date/time fixes...\n');
|
|
|
|
const sqlFile = join(__dirname, '..', 'database', 'fix_all_dates_and_times.sql');
|
|
const sql = fs.readFileSync(sqlFile, 'utf8');
|
|
|
|
const pool = new Pool(dbConfig);
|
|
|
|
try {
|
|
// Extract and execute UPDATE statements only (skip SELECT/verification)
|
|
const updateStatements = sql
|
|
.split(/;[\r\n]+/)
|
|
.map(s => s.trim())
|
|
.filter(s => {
|
|
const upper = s.toUpperCase().trim();
|
|
return upper.startsWith('UPDATE') && !upper.includes('-- Skip');
|
|
});
|
|
|
|
console.log(`Found ${updateStatements.length} UPDATE statements to execute...\n`);
|
|
|
|
for (let i = 0; i < updateStatements.length; i++) {
|
|
const statement = updateStatements[i];
|
|
|
|
try {
|
|
const result = await pool.query(statement);
|
|
console.log(`✅ Update ${i + 1}/${updateStatements.length}: ${result.rowCount || 0} rows affected`);
|
|
} catch (error) {
|
|
console.error(`❌ Error in update ${i + 1}:`, error.message);
|
|
// Continue with next statement
|
|
}
|
|
}
|
|
|
|
console.log('\n✅ All fix statements executed!\n');
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify fixes
|
|
*/
|
|
async function verifyFixes() {
|
|
console.log('\n✅ Verifying fixes...\n');
|
|
|
|
const queries = {
|
|
'CreatedDate Verification': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format,
|
|
COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid,
|
|
COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_count,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`,
|
|
|
|
'CreatedTime Verification': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_format,
|
|
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as minutes_only_valid,
|
|
COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~ '^\\d{1,2}:\\d{2}(:\\d{2})?\\s+[AP]M$') as still_invalid,
|
|
COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_count,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`,
|
|
|
|
'ExpirationDate Verification': `
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format,
|
|
COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid,
|
|
COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_count,
|
|
COUNT(*) as total_rows
|
|
FROM "OptionsFlow_monthly"
|
|
`
|
|
};
|
|
|
|
for (const [name, query] of Object.entries(queries)) {
|
|
console.log(`\n${name}:`);
|
|
console.log('─'.repeat(50));
|
|
try {
|
|
const results = await executeSQL(query);
|
|
if (results.length > 0) {
|
|
const row = results[0];
|
|
console.table(row);
|
|
|
|
// Show percentage
|
|
if (row.total_rows > 0) {
|
|
const validPct = ((row.valid_format || 0) / row.total_rows * 100).toFixed(2);
|
|
const invalidPct = ((row.still_invalid || 0) / row.total_rows * 100).toFixed(2);
|
|
console.log(` Valid: ${validPct}% | Invalid: ${invalidPct}%`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error running ${name}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function
|
|
*/
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const command = args[0] || 'all';
|
|
|
|
console.log('🗄️ Database Date/Time Fix Script\n');
|
|
console.log(`Connecting to: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}\n`);
|
|
|
|
try {
|
|
if (command === 'analyze' || command === 'all') {
|
|
await analyzeDatabase();
|
|
}
|
|
|
|
if (command === 'fix' || command === 'all') {
|
|
if (command === 'all') {
|
|
console.log('\n' + '='.repeat(60));
|
|
console.log('Press Enter to continue with fixes, or Ctrl+C to cancel...');
|
|
// Note: In a real implementation, you might want to add a prompt here
|
|
}
|
|
await runFix();
|
|
}
|
|
|
|
if (command === 'verify' || command === 'all') {
|
|
await verifyFixes();
|
|
}
|
|
|
|
console.log('\n✨ Done!\n');
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Error:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
main();
|
|
}
|
|
|
|
export { analyzeDatabase, runFix, verifyFixes };
|
|
|