@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
JavaScript
;
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