const mysql = require('mysql2/promise'); const path = require('path'); const fs = require('fs'); require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); const dbConfig = { host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, multipleStatements: true }; function outputProgress(data) { if (!data.status) { data = { status: 'running', ...data }; } console.log(JSON.stringify(data)); } // Explicitly define all metrics-related tables in dependency order const METRICS_TABLES = [ 'brand_metrics', 'brand_time_metrics', 'category_forecasts', 'category_metrics', 'category_sales_metrics', 'category_time_metrics', 'product_metrics', 'product_time_aggregates', 'sales_forecasts', 'temp_purchase_metrics', 'temp_sales_metrics', 'vendor_metrics', 'vendor_time_metrics', 'vendor_details' ]; // Split SQL into individual statements function splitSQLStatements(sql) { sql = sql.replace(/\r\n/g, '\n'); let statements = []; let currentStatement = ''; let inString = false; let stringChar = ''; for (let i = 0; i < sql.length; i++) { const char = sql[i]; const nextChar = sql[i + 1] || ''; if ((char === "'" || char === '"') && sql[i - 1] !== '\\') { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar) { inString = false; } } if (!inString && char === '-' && nextChar === '-') { while (i < sql.length && sql[i] !== '\n') i++; continue; } if (!inString && char === '/' && nextChar === '*') { i += 2; while (i < sql.length && (sql[i] !== '*' || sql[i + 1] !== '/')) i++; i++; continue; } if (!inString && char === ';') { if (currentStatement.trim()) { statements.push(currentStatement.trim()); } currentStatement = ''; } else { currentStatement += char; } } if (currentStatement.trim()) { statements.push(currentStatement.trim()); } return statements; } async function resetMetrics() { let connection; try { outputProgress({ operation: 'Starting metrics reset', message: 'Connecting to database...' }); connection = await mysql.createConnection(dbConfig); await connection.beginTransaction(); // First verify current state const [initialTables] = await connection.query(` SELECT TABLE_NAME as name FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (?) `, [METRICS_TABLES]); outputProgress({ operation: 'Initial state', message: `Found ${initialTables.length} existing metrics tables: ${initialTables.map(t => t.name).join(', ')}` }); // Disable foreign key checks at the start await connection.query('SET FOREIGN_KEY_CHECKS = 0'); // Drop all metrics tables in reverse order to handle dependencies outputProgress({ operation: 'Dropping metrics tables', message: 'Removing existing metrics tables...' }); for (const table of [...METRICS_TABLES].reverse()) { try { await connection.query(`DROP TABLE IF EXISTS ${table}`); // Verify the table was actually dropped const [checkDrop] = await connection.query(` SELECT COUNT(*) as count FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? `, [table]); if (checkDrop[0].count > 0) { throw new Error(`Failed to drop table ${table} - table still exists`); } outputProgress({ operation: 'Table dropped', message: `Successfully dropped table: ${table}` }); } catch (err) { outputProgress({ status: 'error', operation: 'Drop table error', message: `Error dropping table ${table}: ${err.message}` }); throw err; } } // Verify all tables were dropped const [afterDrop] = await connection.query(` SELECT TABLE_NAME as name FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (?) `, [METRICS_TABLES]); if (afterDrop.length > 0) { throw new Error(`Failed to drop all tables. Remaining tables: ${afterDrop.map(t => t.name).join(', ')}`); } // Read metrics schema outputProgress({ operation: 'Reading schema', message: 'Loading metrics schema file...' }); const schemaPath = path.resolve(__dirname, '../db/metrics-schema.sql'); if (!fs.existsSync(schemaPath)) { throw new Error(`Schema file not found at: ${schemaPath}`); } const schemaSQL = fs.readFileSync(schemaPath, 'utf8'); const statements = splitSQLStatements(schemaSQL); outputProgress({ operation: 'Schema loaded', message: `Found ${statements.length} SQL statements to execute` }); // Execute schema statements for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; try { await connection.query(stmt); // Check for warnings const [warnings] = await connection.query('SHOW WARNINGS'); if (warnings && warnings.length > 0) { outputProgress({ status: 'warning', operation: 'SQL Warning', message: { statement: i + 1, warnings: warnings } }); } // If this is a CREATE TABLE statement, verify the table was created if (stmt.trim().toLowerCase().startsWith('create table')) { const tableName = stmt.match(/create\s+table\s+(?:if\s+not\s+exists\s+)?`?(\w+)`?/i)?.[1]; if (tableName) { const [checkCreate] = await connection.query(` SELECT TABLE_NAME as name, CREATE_TIME as created FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? `, [tableName]); if (checkCreate.length === 0) { throw new Error(`Failed to create table ${tableName} - table does not exist after CREATE statement`); } outputProgress({ operation: 'Table created', message: `Successfully created table: ${tableName} at ${checkCreate[0].created}` }); } } outputProgress({ operation: 'SQL Progress', message: { statement: i + 1, total: statements.length, preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : '') } }); } catch (sqlError) { outputProgress({ status: 'error', operation: 'SQL Error', message: { error: sqlError.message, sqlState: sqlError.sqlState, errno: sqlError.errno, statement: stmt, statementNumber: i + 1 } }); throw sqlError; } } // Re-enable foreign key checks after all tables are created await connection.query('SET FOREIGN_KEY_CHECKS = 1'); // Verify metrics tables were created outputProgress({ operation: 'Verifying metrics tables', message: 'Checking all metrics tables were created...' }); const [metricsTablesResult] = await connection.query(` SELECT TABLE_NAME as name, TABLE_ROWS as \`rows\`, CREATE_TIME as created FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (?) `, [METRICS_TABLES]); outputProgress({ operation: 'Tables found', message: `Found ${metricsTablesResult.length} tables: ${metricsTablesResult.map(t => `${t.name} (created: ${t.created})` ).join(', ')}` }); const existingMetricsTables = metricsTablesResult.map(t => t.name); const missingMetricsTables = METRICS_TABLES.filter(t => !existingMetricsTables.includes(t)); if (missingMetricsTables.length > 0) { // Do one final check of the actual tables const [finalCheck] = await connection.query('SHOW TABLES'); outputProgress({ operation: 'Final table check', message: `All database tables: ${finalCheck.map(t => Object.values(t)[0]).join(', ')}` }); throw new Error(`Failed to create metrics tables: ${missingMetricsTables.join(', ')}`); } await connection.commit(); outputProgress({ status: 'complete', operation: 'Reset complete', message: 'All metrics tables have been reset successfully' }); } catch (error) { outputProgress({ status: 'error', operation: 'Reset failed', message: error.message, stack: error.stack }); if (connection) { await connection.rollback(); // Make sure to re-enable foreign key checks even if there's an error await connection.query('SET FOREIGN_KEY_CHECKS = 1').catch(() => {}); } throw error; } finally { if (connection) { // One final attempt to ensure foreign key checks are enabled await connection.query('SET FOREIGN_KEY_CHECKS = 1').catch(() => {}); await connection.end(); } } } // Export if required as a module if (typeof module !== 'undefined' && module.exports) { module.exports = resetMetrics; } // Run if called from command line if (require.main === module) { resetMetrics().catch(error => { console.error('Error:', error); process.exit(1); }); }