Skip to main content
Glama
query-optimizer.js25 kB
/** * Query Optimization and Analysis Engine * Provides intelligent query analysis, bottleneck detection, and optimization recommendations */ export class QueryOptimizer { constructor(connectionManager, config = {}) { this.connectionManager = connectionManager; this.config = { // Complexity score weights complexityWeights: { joins: 2.0, subqueries: 1.5, aggregates: 1.2, unions: 1.8, ctes: 1.3, windowFunctions: 2.5, ...config.complexityWeights }, // Performance thresholds thresholds: { slowQueryMs: config.slowQueryMs || 5000, highIoReads: config.highIoReads || 10000, highCpuMs: config.highCpuMs || 3000, lowImpactThreshold: config.lowImpactThreshold || 20, mediumImpactThreshold: config.mediumImpactThreshold || 50, highImpactThreshold: config.highImpactThreshold || 80, ...config.thresholds }, // Feature flags features: { enableAdvancedAnalysis: config.enableAdvancedAnalysis ?? true, enableIndexRecommendations: config.enableIndexRecommendations ?? true, enableQueryRewriting: config.enableQueryRewriting ?? true, ...config.features } }; } /** * Analyzes a SQL query and provides comprehensive optimization insights * @param {string} query - SQL query to analyze * @param {object} executionStats - Execution statistics from SQL Server * @param {object} planData - Execution plan data * @returns {object} Complete query analysis */ analyzeQuery(query, executionStats = {}, planData = {}) { // Input validation to prevent null pointer exceptions if (!query || typeof query !== 'string') { throw new Error('Query must be a non-empty string'); } try { const analysis = { query: query.trim(), query_type: this.determineQueryType(query), complexity_score: this.calculateComplexityScore(query), estimated_cost: planData.TotalSubtreeCost || 0, table_access_methods: this.extractTableAccessMethods(planData), join_algorithms: this.extractJoinAlgorithms(planData), operators: this.extractOperators(planData), bottlenecks: this.identifyBottlenecks(query, executionStats, planData), performance_warnings: this.generatePerformanceWarnings(query, executionStats), optimization_suggestions: this.generateOptimizationSuggestions( query, executionStats, planData ), is_modification_query: this.isModificationQuery(query) }; return analysis; } catch (error) { // Log the error and return a safe fallback analysis console.error('Error during query analysis:', error.message); return { query: query.trim(), query_type: 'UNKNOWN', complexity_score: 0, estimated_cost: 0, table_access_methods: [], join_algorithms: [], operators: [], bottlenecks: [], performance_warnings: ['Query analysis failed due to parsing error'], optimization_suggestions: [], is_modification_query: false, error: 'Query analysis failed' }; } } /** * Determines the type of SQL query * @param {string} query - SQL query * @returns {string} Query type classification */ determineQueryType(query) { // Null safety check to prevent runtime errors if (!query || typeof query !== 'string') { return 'UNKNOWN'; } const trimmed = query.trim().toUpperCase(); if (trimmed.startsWith('SELECT')) { if (this.containsJoins(query) && this.containsAggregation(query)) { return 'SELECT_WITH_JOIN_AND_AGGREGATION'; } else if (this.containsJoins(query)) { return 'SELECT_WITH_JOIN'; } else if (this.containsAggregation(query)) { return 'SELECT_WITH_AGGREGATION'; } else if (this.containsSubqueries(query)) { return 'SELECT_WITH_SUBQUERY'; } return 'SELECT_SIMPLE'; } else if (trimmed.startsWith('INSERT')) { return 'INSERT'; } else if (trimmed.startsWith('UPDATE')) { return 'UPDATE'; } else if (trimmed.startsWith('DELETE')) { return 'DELETE'; } else if (trimmed.startsWith('WITH')) { return 'CTE_QUERY'; } else if (trimmed.startsWith('MERGE')) { return 'MERGE'; } return 'UNKNOWN'; } /** * Calculates query complexity score based on various factors * @param {string} query - SQL query * @returns {number} Complexity score (0-100) */ calculateComplexityScore(query) { // Null safety check to prevent runtime errors if (!query || typeof query !== 'string') { return 0; } let score = 1; // Base score const upperQuery = query.toUpperCase(); // Count joins const joinCount = ( upperQuery.match(/\b(INNER JOIN|LEFT JOIN|RIGHT JOIN|FULL JOIN|CROSS JOIN|JOIN)\b/g) || [] ).length; score += joinCount * this.config.complexityWeights.joins; // Count subqueries const subqueryCount = this.countSubqueries(query); score += subqueryCount * this.config.complexityWeights.subqueries; // Count aggregates const aggregateCount = (upperQuery.match(/\b(COUNT|SUM|AVG|MIN|MAX|GROUP BY|HAVING)\b/g) || []) .length; score += aggregateCount * this.config.complexityWeights.aggregates; // Count unions const unionCount = (upperQuery.match(/\bUNION\b/g) || []).length; score += unionCount * this.config.complexityWeights.unions; // Count CTEs const cteCount = (upperQuery.match(/\bWITH\b/g) || []).length; score += cteCount * this.config.complexityWeights.ctes; // Count window functions const windowFunctionCount = (upperQuery.match(/\bOVER\s*\(/g) || []).length; score += windowFunctionCount * this.config.complexityWeights.windowFunctions; // Normalize to 0-100 scale (capped at 100) return Math.min(Math.round(score), 100); } /** * Identifies potential bottlenecks in query execution * @param {string} query - SQL query * @param {object} stats - Execution statistics * @param {object} planData - Execution plan data * @returns {array} List of identified bottlenecks */ identifyBottlenecks(query, stats, planData) { const bottlenecks = []; // High I/O bottleneck if (stats.avg_logical_reads > this.config.thresholds.highIoReads) { bottlenecks.push({ type: 'HIGH_IO', severity: 'HIGH', description: `High logical reads detected (${stats.avg_logical_reads})`, recommendation: 'Consider adding indexes or optimizing WHERE clauses' }); } // CPU intensive operations if (stats.avg_cpu_time > this.config.thresholds.highCpuMs) { bottlenecks.push({ type: 'CPU_INTENSIVE', severity: 'MEDIUM', description: `High CPU usage detected (${stats.avg_cpu_time}ms)`, recommendation: 'Review query logic and consider indexing strategies' }); } // Large table scans if (this.hasTableScans(query, planData)) { bottlenecks.push({ type: 'LARGE_TABLE_SCAN', severity: 'MEDIUM', description: 'Full table scan detected', recommendation: 'Consider adding index on frequently queried columns' }); } // Inefficient joins if (this.hasInfficientJoins(query, planData)) { bottlenecks.push({ type: 'INEFFICIENT_JOIN', severity: 'HIGH', description: 'Inefficient join algorithm detected', recommendation: 'Review join conditions and ensure proper indexing' }); } return bottlenecks; } /** * Generates performance warnings based on query analysis * @param {string} query - SQL query * @param {object} stats - Execution statistics * @returns {array} List of performance warnings */ generatePerformanceWarnings(query, _stats) { if (!query || typeof query !== 'string') { return []; } const warnings = []; const upperQuery = query.toUpperCase(); // SELECT * warnings if (upperQuery.includes('SELECT *')) { warnings.push('SELECT * can impact performance - specify only needed columns'); } // Missing WHERE clause if ( upperQuery.includes('SELECT') && !upperQuery.includes('WHERE') && !upperQuery.includes('JOIN') ) { warnings.push('Query without WHERE clause may return large result sets'); } // LEFT JOIN with large tables if (upperQuery.includes('LEFT JOIN') && this.containsLargeTables(query)) { warnings.push('LEFT JOIN with large table may cause performance issues'); } // GROUP BY without covering index if (upperQuery.includes('GROUP BY')) { warnings.push('GROUP BY operation requires sorting - consider covering index'); } // ORDER BY without index if (upperQuery.includes('ORDER BY') && !this.hasOrderByIndex(query)) { warnings.push('ORDER BY without supporting index may cause slow sorting'); } // High execution count with slow performance if (_stats && _stats.execution_count > 100 && _stats.avg_duration > 1000) { warnings.push('Frequently executed slow query - high optimization priority'); } return warnings; } /** * Generates specific optimization suggestions * @param {string} query - SQL query * @param {object} stats - Execution statistics * @param {object} planData - Execution plan data * @returns {array} List of optimization suggestions */ generateOptimizationSuggestions(query, stats, _planData) { const suggestions = []; // Index recommendations if (this.config.features.enableIndexRecommendations) { const indexSuggestions = this.generateIndexRecommendations(query, stats, _planData); suggestions.push(...indexSuggestions); } // Query rewriting suggestions if (this.config.features.enableQueryRewriting) { const rewritingSuggestions = this.generateQueryRewritingSuggestions(query, stats); suggestions.push(...rewritingSuggestions); } // Performance tuning suggestions const tuningSuggestions = this.generateTuningSuggestions(query, stats, _planData); suggestions.push(...tuningSuggestions); return suggestions.sort( (a, b) => this.prioritySortOrder(a.priority) - this.prioritySortOrder(b.priority) ); } /** * Generates index optimization recommendations * @param {string} query - SQL query * @param {object} stats - Execution statistics * @param {object} planData - Execution plan data * @returns {array} Index recommendations */ generateIndexRecommendations(query, stats, _planData) { const suggestions = []; const upperQuery = query.toUpperCase(); // Analyze WHERE clauses for index opportunities const whereColumns = this.extractWhereColumns(query); if (whereColumns.length > 0) { const impact = this.calculateIndexImpact(stats, 'WHERE'); suggestions.push({ type: 'INDEX_RECOMMENDATION', priority: impact > 70 ? 'HIGH' : 'MEDIUM', suggestion: `CREATE INDEX IX_${whereColumns.join('_')} ON [table](${whereColumns.join(', ')})`, estimated_improvement: `${Math.round(impact)}% performance gain`, reason: 'Optimize WHERE clause filtering' }); } // Analyze JOIN conditions const joinColumns = this.extractJoinColumns(query); if (joinColumns.length > 0) { const impact = this.calculateIndexImpact(stats, 'JOIN'); suggestions.push({ type: 'INDEX_RECOMMENDATION', priority: impact > 60 ? 'HIGH' : 'MEDIUM', suggestion: `Consider covering index for JOIN operations on columns: ${joinColumns.join(', ')}`, estimated_improvement: `${Math.round(impact)}% performance gain`, reason: 'Optimize JOIN performance' }); } // Analyze ORDER BY clauses if (upperQuery.includes('ORDER BY')) { const orderByColumns = this.extractOrderByColumns(query); if (orderByColumns.length > 0) { suggestions.push({ type: 'INDEX_RECOMMENDATION', priority: 'MEDIUM', suggestion: `CREATE INDEX IX_OrderBy ON [table](${orderByColumns.join(', ')})`, estimated_improvement: '40% performance gain', reason: 'Eliminate sorting overhead for ORDER BY' }); } } return suggestions; } /** * Generates query rewriting suggestions * @param {string} query - SQL query * @param {object} stats - Execution statistics * @returns {array} Query rewriting suggestions */ generateQueryRewritingSuggestions(query, _stats) { const suggestions = []; const upperQuery = query.toUpperCase(); // EXISTS vs LEFT JOIN optimization if (upperQuery.includes('LEFT JOIN') && this.canUseExists(query)) { suggestions.push({ type: 'QUERY_REWRITE', priority: 'MEDIUM', suggestion: 'Consider EXISTS instead of LEFT JOIN if you only need users with orders', estimated_improvement: '25% performance gain', reason: 'EXISTS can be more efficient than LEFT JOIN for existence checks' }); } // IN vs EXISTS optimization if (upperQuery.includes(' IN (SELECT')) { suggestions.push({ type: 'QUERY_REWRITE', priority: 'MEDIUM', suggestion: 'Consider replacing IN (SELECT...) with EXISTS for better performance', estimated_improvement: '30% performance gain', reason: 'EXISTS often performs better than IN with subqueries' }); } // DISTINCT optimization if (upperQuery.includes('SELECT DISTINCT') && upperQuery.includes('JOIN')) { suggestions.push({ type: 'QUERY_REWRITE', priority: 'LOW', suggestion: 'Review if DISTINCT is necessary with proper JOIN conditions', estimated_improvement: '15% performance gain', reason: 'Unnecessary DISTINCT adds overhead' }); } return suggestions; } /** * Generates general performance tuning suggestions * @param {string} query - SQL query * @param {object} stats - Execution statistics * @param {object} planData - Execution plan data * @returns {array} Tuning suggestions */ generateTuningSuggestions(query, stats, _planData) { const suggestions = []; // High execution count optimization if (stats.execution_count > 500 && stats.avg_duration > 100) { suggestions.push({ type: 'PERFORMANCE_TUNING', priority: 'CRITICAL', suggestion: 'This frequently executed query needs immediate optimization', estimated_improvement: '50% average system performance gain', reason: `Query executed ${stats.execution_count} times with ${stats.avg_duration}ms average` }); } // Memory optimization if (stats.avg_logical_reads > 50000) { suggestions.push({ type: 'MEMORY_OPTIMIZATION', priority: 'HIGH', suggestion: 'Consider query optimization to reduce memory pressure', estimated_improvement: '35% memory usage reduction', reason: `High logical reads detected (${stats.avg_logical_reads})` }); } // Parameterization suggestion if (this.hasLiterals(query)) { suggestions.push({ type: 'PARAMETERIZATION', priority: 'MEDIUM', suggestion: 'Consider parameterizing literal values for better plan reuse', estimated_improvement: '20% compilation overhead reduction', reason: 'Hard-coded values prevent plan reuse' }); } return suggestions; } /** * Calculates estimated impact of an index recommendation * @param {object} stats - Execution statistics * @param {string} type - Type of index (WHERE, JOIN, ORDER) * @returns {number} Impact percentage (0-100) */ calculateIndexImpact(stats, type) { let baseImpact = 30; // Base improvement for any index // Factor in current performance metrics if (stats.avg_duration > this.config.thresholds.slowQueryMs) { baseImpact += 30; } if (stats.avg_logical_reads > this.config.thresholds.highIoReads) { baseImpact += 25; } if (stats.execution_count > 100) { baseImpact += 15; // High-frequency queries benefit more } // Type-specific adjustments switch (type) { case 'WHERE': baseImpact += 20; // WHERE clause indexes are very effective break; case 'JOIN': baseImpact += 15; // JOIN indexes are moderately effective break; case 'ORDER': baseImpact += 10; // ORDER BY indexes help but less critical break; } return Math.min(baseImpact, 95); // Cap at 95% } // ==================== HELPER METHODS ==================== /** * Checks if query contains joins */ containsJoins(query) { return /\b(INNER JOIN|LEFT JOIN|RIGHT JOIN|FULL JOIN|CROSS JOIN|JOIN)\b/i.test(query); } /** * Checks if query contains aggregation */ containsAggregation(query, _planData) { return /\b(COUNT|SUM|AVG|MIN|MAX|GROUP BY|HAVING)\b/i.test(query); } /** * Checks if query contains subqueries */ containsSubqueries(query, _planData) { return /\(\s*SELECT\b/i.test(query); } /** * Counts number of subqueries */ countSubqueries(query) { const matches = query.match(/\(\s*SELECT\b/gi); return matches ? matches.length : 0; } /** * Checks if query is a modification query */ isModificationQuery(query) { return /^\s*(INSERT|UPDATE|DELETE|MERGE)\b/i.test(query); } /** * Checks for table scans in execution plan */ hasTableScans(query, _planData) { // This would analyze actual execution plan data // For now, we'll use heuristics if (!query || typeof query !== 'string') { return false; } return !query.toUpperCase().includes('WHERE') && query.toUpperCase().includes('SELECT'); } /** * Checks for inefficient joins */ hasInfficientJoins(_query, _planData) { // This would analyze execution plan for nested loop joins on large tables // For now, return false as placeholder return false; } /** * Checks if query contains large tables */ containsLargeTables(_query) { // This would require table statistics // For now, assume any table could be large return true; } /** * Checks if ORDER BY has supporting index */ hasOrderByIndex(_query) { // This would require index information // For now, assume no supporting index return false; } /** * Extracts columns from WHERE clauses */ extractWhereColumns(query) { // Input validation and size limit to prevent ReDoS attacks if (!query || typeof query !== 'string') { return []; } // Limit query size to prevent ReDoS attacks if (query.length > 10000) { // Only log warning in production, suppress during testing if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) { console.warn('Query too large for WHERE clause analysis, skipping'); } return []; } const whereMatch = query.match(/WHERE\s+(.+?)(?:\s+GROUP\s+BY|\s+ORDER\s+BY|\s+HAVING|$)/i); if (!whereMatch) return []; // Simple extraction - would need more sophisticated parsing for production const whereClause = whereMatch[1]; const columns = []; // Extract column names using a safer approach without sanitization // Match column names that appear before comparison operators const patterns = [ /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*>=/g, // >= operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*<=/g, // <= operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*!=/g, // != operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*<>/g, // <> operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*=/g, // = operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*>/g, // > operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*</g, // < operator /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*!/g // ! operator (for NOT patterns) ]; patterns.forEach(pattern => { let match; while ((match = pattern.exec(whereClause)) !== null) { const columnName = match[1].trim(); if (columnName && !columns.includes(columnName)) { columns.push(columnName); } } }); return columns; } /** * Extracts columns from JOIN conditions */ extractJoinColumns(query) { // Input validation and size limit to prevent ReDoS attacks if (!query || typeof query !== 'string') { return []; } // Limit query size to prevent ReDoS attacks if (query.length > 10000) { // Only log warning in production, suppress during testing if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) { console.warn('Query too large for JOIN analysis, skipping'); } return []; } const joinMatches = query.match( /JOIN\s+\w+\s+\w+\s+ON\s+(.+?)(?:\s+WHERE|\s+GROUP|\s+ORDER|$)/gi ); const columns = []; if (joinMatches) { joinMatches.forEach(joinMatch => { const onClause = joinMatch.replace(/.*ON\s+/i, ''); const columnMatches = onClause.match( /\b([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)\b/g ); if (columnMatches) { columns.push(...columnMatches); } }); } return [...new Set(columns)]; // Remove duplicates } /** * Extracts columns from ORDER BY clause */ extractOrderByColumns(query) { const orderByMatch = query.match(/ORDER\s+BY\s+(.+?)(?:\s+LIMIT|\s+OFFSET|$)/i); if (!orderByMatch) return []; const orderByClause = orderByMatch[1]; const columns = orderByClause .split(',') .map(col => col.trim().replace(/\s+(ASC|DESC)$/i, '')) .filter(col => col.length > 0); return columns; } /** * Checks if LEFT JOIN can be replaced with EXISTS */ canUseExists(query) { // Simple heuristic: if LEFT JOIN is used just for existence check if (!query || typeof query !== 'string') { return false; } return ( query.toUpperCase().includes('LEFT JOIN') && !query.toUpperCase().includes('SELECT') && query.toUpperCase().match(/\.\*/) ); } /** * Checks if query has hard-coded literals */ hasLiterals(query) { // Check for string literals and numeric literals return /'[^']*'/.test(query) || /\b\d+\b/.test(query); } /** * Extracts table access methods from execution plan */ extractTableAccessMethods(_planData) { // This would parse actual execution plan XML/JSON // For now, return mock data structure return []; } /** * Extracts join algorithms from execution plan */ extractJoinAlgorithms(_planData) { // This would parse actual execution plan data return []; } /** * Extracts operators from execution plan */ extractOperators(_planData) { // This would parse execution plan operators return []; } /** * Gets priority sort order for suggestions */ prioritySortOrder(priority) { const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }; return order[priority] || 999; } /** * Analyzes index usage patterns and provides recommendations * @param {string} database - Database name to analyze * @returns {object} Index analysis and recommendations */ async analyzeIndexUsage(database) { // Basic index recommendations - this would normally query SQL Server DMVs return { database, timestamp: new Date().toISOString(), recommendations: [ { type: 'missing_index', priority: 'high', suggestion: 'Consider adding indexes on frequently queried columns', impact: 'Could improve query performance by 50-80%', table: 'example_table', columns: ['column1', 'column2'] } ], unusedIndexes: [], duplicateIndexes: [], fragmentedIndexes: [] }; } /** * Provides general optimization insights for a database * @param {string} database - Database name * @returns {object} Optimization insights */ async getIndexRecommendations(query) { const pool = this.connectionManager.getPool(); if (!pool) { throw new Error('Not connected to any server'); } if (!query) { throw new Error('Query is required for getIndexRecommendations'); } // Placeholder for actual implementation return Promise.resolve([]); } /** * Provides general optimization insights for a database * @param {string} database - Database name * @returns {object} Optimization insights */ async getOptimizationInsights(query) { const pool = this.connectionManager.getPool(); if (!pool) { throw new Error('Not connected to any server'); } if (!query) { throw new Error('Query is required for getOptimizationInsights'); } // Placeholder for actual implementation return Promise.resolve({}); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/egarcia74/warp-sql-server-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server