UNPKG

@vasoyaprince14/sql-analyzer

Version:

🚀 Enhanced SQL database analyzer with AI-powered insights, comprehensive security analysis, RLS policy auditing, and beautiful HTML reports

479 lines 20.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.configPresets = exports.ConfigManager = exports.EnhancedSQLAnalyzer = void 0; const pg_1 = require("pg"); const enhanced_database_auditor_1 = require("./enhanced-database-auditor"); const enhanced_report_generator_1 = require("./enhanced-report-generator"); const config_1 = require("./config"); Object.defineProperty(exports, "ConfigManager", { enumerable: true, get: function () { return config_1.ConfigManager; } }); Object.defineProperty(exports, "configPresets", { enumerable: true, get: function () { return config_1.configPresets; } }); const fs_1 = require("fs"); const path_1 = require("path"); class EnhancedSQLAnalyzer { constructor(connectionConfig, options) { // Initialize configuration let baseConfig = new config_1.ConfigManager(); // Apply preset if specified if (options?.preset) { baseConfig.updateConfig(config_1.configPresets[options.preset]); } // Apply custom config if (options?.customConfig) { baseConfig.updateConfig(options.customConfig); } // Override with connection and analysis options baseConfig.updateConfig({ database: { ...connectionConfig }, analysis: { includeAIInsights: options?.includeAI ?? false }, reporting: { format: options?.format ?? 'html', outputPath: options?.outputPath ?? './reports' } }); this.configManager = baseConfig; // Validate configuration const validation = this.configManager.validateConfig(); if (!validation.valid) { throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`); } // Initialize database client this.client = new pg_1.Client(this.getConnectionConfig()); // Initialize components const config = this.configManager.getConfig(); this.auditor = new enhanced_database_auditor_1.EnhancedDatabaseHealthAuditor(this.client, { enableAI: config.ai?.enabled, openaiApiKey: config.ai?.apiKey, openaiModel: config.ai?.model, openaiTemperature: config.ai?.temperature }); this.reportGenerator = new enhanced_report_generator_1.EnhancedReportGenerator(); } /** * Perform comprehensive database analysis */ async analyze() { const config = this.configManager.getConfig(); try { console.log('🔗 Connecting to database...'); await this.client.connect(); console.log('🔍 Starting comprehensive database analysis...'); const startTime = Date.now(); const report = await this.auditor.performComprehensiveAudit(); const duration = Date.now() - startTime; console.log(`✅ Analysis completed in ${duration}ms`); return report; } catch (error) { console.error('❌ Analysis failed:', error); throw error; } finally { await this.client.end(); } } /** * Generate and save report */ async generateReport(report) { const config = this.configManager.getConfig(); const format = config.reporting?.format || 'html'; const outputPath = config.reporting?.outputPath || './reports'; // Compute trends vs last run and persist lightweight summary for ALL formats try { const fsnode = await Promise.resolve().then(() => __importStar(require('fs'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const lastSummaryPath = path.join(outputPath, 'last-summary.json'); let prev = null; if (fsnode.existsSync(lastSummaryPath)) { try { prev = JSON.parse(fsnode.readFileSync(lastSummaryPath, 'utf-8')); } catch { } } const summaryForTrend = this.generateSummary(report); const currentSummary = { overall: report.schemaHealth?.overall, securityIssues: report.securityAnalysis?.vulnerabilities?.length || 0, missingIndexes: report.indexAnalysis?.missingIndexes?.length || 0, bloatedTables: report.tableAnalysis?.tablesWithBloat?.length || 0, totalIssues: summaryForTrend.totalIssues, criticalIssues: summaryForTrend.criticalIssues, generatedAt: new Date().toISOString() }; const trend = prev ? { overallDelta: Number(((currentSummary.overall || 0) - (prev.overall || 0)).toFixed(1)), securityDelta: (currentSummary.securityIssues || 0) - (prev.securityIssues || 0), missingIdxDelta: (currentSummary.missingIndexes || 0) - (prev.missingIndexes || 0), bloatDelta: (currentSummary.bloatedTables || 0) - (prev.bloatedTables || 0), totalIssuesDelta: (currentSummary.totalIssues || 0) - (prev.totalIssues || 0), criticalIssuesDelta: (currentSummary.criticalIssues || 0) - (prev.criticalIssues || 0) } : null; report.__trend = trend; try { fsnode.mkdirSync(outputPath, { recursive: true }); } catch { } try { fsnode.writeFileSync(lastSummaryPath, JSON.stringify(currentSummary, null, 2)); } catch { } } catch { } let reportContent; let fileName; let fileExtension; switch (format) { case 'html': // Attach report to AI insights block for strategic recs const aiAttached = report.aiInsights ? { ...report.aiInsights, __report: report } : undefined; const reportWithAI = aiAttached ? { ...report, aiInsights: aiAttached } : report; reportContent = this.reportGenerator.generateEnhancedHTMLReport(reportWithAI); fileExtension = 'html'; fileName = `database-health-report-${this.getTimestamp()}.html`; break; case 'cli': reportContent = this.reportGenerator.generateEnhancedCLIReport(report); fileExtension = 'txt'; fileName = `database-health-report-${this.getTimestamp()}.txt`; break; case 'json': reportContent = JSON.stringify(report, null, 2); fileExtension = 'json'; fileName = `database-health-report-${this.getTimestamp()}.json`; break; case 'md': { const lines = []; lines.push(`# Database Health Report`); lines.push(`Generated: ${new Date().toISOString()}`); lines.push(''); lines.push(`## Summary`); lines.push(`- Health Score: ${report.schemaHealth.overall}/10`); const sec = report.securityAnalysis?.vulnerabilities?.length || 0; const missIdx = report.indexAnalysis?.missingIndexes?.length || 0; const bloat = report.tableAnalysis?.tablesWithBloat?.length || 0; lines.push(`- Security issues: ${sec}`); lines.push(`- Missing indexes: ${missIdx}`); lines.push(`- Bloated tables: ${bloat}`); lines.push(''); if (report.schemaHealth.issues.length) { lines.push('## Issues'); report.schemaHealth.issues.slice(0, 50).forEach(i => { lines.push(`- [${i.severity}] ${i.description}${i.sqlFix ? `\n - SQL: \`${i.sqlFix}\`` : ''}`); }); lines.push(''); } if (report.optimizationRecommendations.length) { lines.push('## Recommendations'); report.optimizationRecommendations.slice(0, 50).forEach(r => { lines.push(`- (${r.priority}) ${r.title}: ${r.description}`); if (r.sqlCommands?.length) { lines.push(' - SQL:'); r.sqlCommands.forEach(cmd => lines.push(` - \`${cmd}\``)); } }); lines.push(''); } reportContent = lines.join('\n'); fileExtension = 'md'; fileName = `database-health-report-${this.getTimestamp()}.md`; break; } default: throw new Error(`Unsupported report format: ${format}`); } // Ensure output directory exists await this.ensureDirectoryExists(outputPath); // Write report file const fullPath = (0, path_1.join)(outputPath, fileName); await fs_1.promises.writeFile(fullPath, reportContent, 'utf-8'); console.log(`📄 Report saved to: ${fullPath}`); return fullPath; } /** * Analyze and generate report in one step */ async analyzeAndReport(options) { const onProgress = options?.onProgress || (() => { }); onProgress('Starting analysis...', 0); // Perform analysis const report = await this.analyze(); onProgress('Analysis complete, generating report...', 70); // Generate summary const summary = this.generateSummary(report); onProgress('Summary generated...', 85); let reportPath; // Save report unless skipped if (!options?.skipSave) { reportPath = await this.generateReport(report); onProgress('Report saved...', 95); } // Optionally export aggregated SQL fixes if (options?.exportSql) { try { const fixes = this.collectSqlFixes(report); const out = this.configManager.getConfig().reporting?.outputPath || './reports'; await this.ensureDirectoryExists(out); const { join } = await Promise.resolve().then(() => __importStar(require('path'))); const { promises: fsp } = await Promise.resolve().then(() => __importStar(require('fs'))); const safePath = join(out, `copy-safe.sql`); const destructivePath = join(out, `copy-destructive.sql`); await fsp.writeFile(safePath, fixes.safe.join('\n') + '\n', 'utf-8'); await fsp.writeFile(destructivePath, fixes.destructive.join('\n') + '\n', 'utf-8'); } catch { } } onProgress('Complete!', 100); return { reportPath, report: options?.returnReport ? report : undefined, summary }; } /** * Generate executive summary */ generateSummary(report) { const criticalIssues = [ ...report.securityAnalysis.vulnerabilities.filter(v => v.severity === 'critical'), ...report.performanceIssues.filter(i => i.severity === 'critical') ]; const totalIssues = report.securityAnalysis.vulnerabilities.length + report.performanceIssues.length + report.tableAnalysis.tablesWithoutPK.length + report.tableAnalysis.tablesWithBloat.length; return { overallScore: report.schemaHealth.overall, totalIssues, criticalIssues: criticalIssues.length, securityRisk: this.calculateSecurityRisk(report), performanceRisk: this.calculatePerformanceRisk(report), costSavingsPotential: report.costAnalysis.optimizationSavings.monthly, topRecommendations: this.getTopRecommendations(report), estimatedImplementationTime: this.estimateImplementationTime(report), riskLevel: this.calculateOverallRisk(report) }; } /** * Get configuration for easy access */ getConfig() { return this.configManager.getConfig(); } /** * Update configuration */ updateConfig(updates) { this.configManager.updateConfig(updates); } getConnectionConfig() { const dbConfig = this.configManager.getConfig().database; if (dbConfig?.connectionString) { return { connectionString: dbConfig.connectionString }; } return { host: dbConfig?.host, port: dbConfig?.port, database: dbConfig?.database, user: dbConfig?.user, password: dbConfig?.password, ssl: dbConfig?.ssl }; } async ensureDirectoryExists(dirPath) { try { await fs_1.promises.access(dirPath); } catch { await fs_1.promises.mkdir(dirPath, { recursive: true }); } } getTimestamp() { return new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); } calculateSecurityRisk(report) { const criticalVulns = report.securityAnalysis.vulnerabilities.filter(v => v.severity === 'critical').length; const highVulns = report.securityAnalysis.vulnerabilities.filter(v => v.severity === 'high').length; if (criticalVulns > 0) return 'critical'; if (highVulns > 2) return 'high'; if (highVulns > 0 || report.securityAnalysis.vulnerabilities.length > 3) return 'medium'; return 'low'; } calculatePerformanceRisk(report) { const criticalIssues = report.performanceIssues.filter(i => i.severity === 'critical').length; const highIssues = report.performanceIssues.filter(i => i.severity === 'high').length; const bloatedTables = report.tableAnalysis.tablesWithBloat.length; if (criticalIssues > 0) return 'critical'; if (highIssues > 1 || bloatedTables > 5) return 'high'; if (highIssues > 0 || bloatedTables > 2) return 'medium'; return 'low'; } calculateOverallRisk(report) { const securityRisk = this.calculateSecurityRisk(report); const performanceRisk = this.calculatePerformanceRisk(report); if (securityRisk === 'critical' || performanceRisk === 'critical') return 'critical'; if (securityRisk === 'high' || performanceRisk === 'high') return 'high'; if (securityRisk === 'medium' || performanceRisk === 'medium') return 'medium'; return 'low'; } getTopRecommendations(report) { const recommendations = []; // Add security recommendations report.securityAnalysis.vulnerabilities .filter(v => v.severity === 'critical' || v.severity === 'high') .forEach(v => { recommendations.push({ text: `Security: ${v.description}`, priority: v.severity === 'critical' ? 10 : 8 }); }); // Add performance recommendations report.performanceIssues .filter(i => i.severity === 'critical' || i.severity === 'high') .forEach(i => { recommendations.push({ text: `Performance: ${i.description}`, priority: i.severity === 'critical' ? 9 : 7 }); }); // Add table bloat recommendations if (report.tableAnalysis.tablesWithBloat.length > 0) { recommendations.push({ text: `Clean up ${report.tableAnalysis.tablesWithBloat.length} bloated tables`, priority: 6 }); } // Add AI insights if available if (report.aiInsights?.priorityRecommendations) { report.aiInsights.priorityRecommendations.forEach(rec => { recommendations.push({ text: `AI Insight: ${rec}`, priority: 5 }); }); } return recommendations .sort((a, b) => b.priority - a.priority) .slice(0, 5) .map(r => r.text); } collectSqlFixes(report) { const safe = []; const destructive = []; // Schema issues fixes for (const issue of report.schemaHealth.issues) { if (!issue.sqlFix) continue; const isDestructive = /drop\s+|vacuum\s+full|reindex|alter\s+table\s+.*\s+drop/i.test(issue.sqlFix); (isDestructive ? destructive : safe).push(issue.sqlFix); } // Index recommendations for (const rec of report.indexAnalysis.recommendations || []) { if (rec.sql) { const sql = rec.sql; const isDestructive = /drop\s+index/i.test(sql); (isDestructive ? destructive : safe).push(sql); } } // Optimization recommendations SQL commands for (const rec of report.optimizationRecommendations) { if (!rec.sqlCommands) continue; for (const cmd of rec.sqlCommands) { const isDestructive = /drop\s+|vacuum\s+full|reindex/i.test(cmd); (isDestructive ? destructive : safe).push(cmd); } } // Security fixes embedded in vulnerabilities for (const v of report.securityAnalysis.vulnerabilities) { if (v.solution) { const isDestructive = /drop\s+|revoke\s+all/i.test(v.solution); (isDestructive ? destructive : safe).push(v.solution); } } // Deduplicate while preserving order const dedupe = (arr) => Array.from(new Set(arr.map(s => s.trim()))).filter(Boolean); return { safe: dedupe(safe), destructive: dedupe(destructive) }; } estimateImplementationTime(report) { const totalIssues = report.securityAnalysis.vulnerabilities.length + report.performanceIssues.length + report.tableAnalysis.tablesWithBloat.length; if (totalIssues === 0) return '0 hours'; if (totalIssues <= 3) return '2-4 hours'; if (totalIssues <= 8) return '1-2 days'; if (totalIssues <= 15) return '3-5 days'; return '1-2 weeks'; } /** * Static method to quickly analyze a database with minimal setup */ static async quickAnalysis(connectionString, options) { const analyzer = new EnhancedSQLAnalyzer({ connectionString }, { format: options?.format || 'html', includeAI: options?.includeAI || false, outputPath: options?.outputPath || './reports', preset: 'development' }); const result = await analyzer.analyzeAndReport({ skipSave: false }); return result.summary; } /** * Static method for CI/CD environments */ static async ciAnalysis(connectionString) { const analyzer = new EnhancedSQLAnalyzer({ connectionString }, { preset: 'ci', format: 'json' }); const result = await analyzer.analyzeAndReport(); const passed = result.summary.criticalIssues === 0 && result.summary.overallScore >= 7; return { passed, score: result.summary.overallScore, criticalIssues: result.summary.criticalIssues, reportPath: result.reportPath }; } } exports.EnhancedSQLAnalyzer = EnhancedSQLAnalyzer; // Export for NPM package exports.default = EnhancedSQLAnalyzer; //# sourceMappingURL=enhanced-sql-analyzer.js.map