UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

346 lines 12.9 kB
/** * QueryOptimizer - Intelligent query optimization for analytics * * Optimizations: * - Index usage analysis * - Query rewriting for better performance * - Join order optimization * - Predicate pushdown * - Unnecessary column elimination * - Query plan caching */ import { getLogger } from '../logging/Logger.js'; export class QueryOptimizer { db; indexCache = new Map(); statsCache = new Map(); queryPlanCache = new Map(); constructor(database) { this.db = database; this.loadIndexInfo(); this.loadTableStats(); } /** * Optimize a hybrid query */ optimize(query, intent) { const optimizations = []; let optimizedQuery = { ...query }; let estimatedCost = 100; // Base cost // 1. Analyze current query plan const queryPlan = this.analyzeQueryPlan(query.sql); // 2. Check if we can use cached results const cacheEligible = this.checkCacheEligibility(query, intent); if (cacheEligible) { optimizations.push({ type: 'cache', description: 'Query eligible for caching', impact: 'high' }); } // 3. Optimize column selection const columnOpt = this.optimizeColumnSelection(optimizedQuery, intent); if (columnOpt.optimized) { optimizedQuery = columnOpt.query; optimizations.push(...columnOpt.optimizations); estimatedCost *= 0.8; // 20% improvement } // 4. Optimize filters and indexes const filterOpt = this.optimizeFilters(optimizedQuery, queryPlan); if (filterOpt.optimized) { optimizedQuery = filterOpt.query; optimizations.push(...filterOpt.optimizations); estimatedCost *= filterOpt.costReduction; } // 5. Optimize joins const joinOpt = this.optimizeJoins(optimizedQuery); if (joinOpt.optimized) { optimizedQuery = joinOpt.query; optimizations.push(...joinOpt.optimizations); estimatedCost *= joinOpt.costReduction; } // 6. Add query hints const hintOpt = this.addQueryHints(optimizedQuery, queryPlan); if (hintOpt.optimized) { optimizedQuery = hintOpt.query; optimizations.push(...hintOpt.optimizations); } getLogger().debug({ originalSQL: query.sql, optimizedSQL: optimizedQuery.sql, optimizationCount: optimizations.length, estimatedCost, cacheEligible }, 'QueryOptimizer: Query optimized'); return { optimizedQuery, optimizations, estimatedCost, useCache: cacheEligible }; } /** * Load index information from database */ loadIndexInfo() { try { const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all(); for (const { name: table } of tables) { const indexes = this.db.prepare("SELECT name, sql FROM sqlite_master WHERE type='index' AND tbl_name=?").all(table); const indexInfo = []; for (const index of indexes) { // Parse index columns from SQL const columnMatch = index.sql?.match(/\((.*?)\)/); if (columnMatch) { const columns = columnMatch[1] .split(',') .map(col => col.trim().replace(/["'`]/g, '')); indexInfo.push({ table, name: index.name, columns, unique: index.sql.includes('UNIQUE') }); } } this.indexCache.set(table, indexInfo); } getLogger().debug({ tableCount: tables.length, totalIndexes: Array.from(this.indexCache.values()).flat().length }, 'QueryOptimizer: Loaded index information'); } catch (error) { getLogger().warn({ error: error.message }, 'QueryOptimizer: Failed to load indexes'); } } /** * Load table statistics */ loadTableStats() { try { const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all(); for (const { name: table } of tables) { try { const countResult = this.db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get(); const indexes = this.indexCache.get(table) || []; this.statsCache.set(table, { table, rowCount: countResult.count, hasIndex: indexes.length > 0, indexedColumns: indexes.flatMap(idx => idx.columns) }); } catch (error) { // Table might not be accessible continue; } } } catch (error) { getLogger().warn({ error: error.message }, 'QueryOptimizer: Failed to load table stats'); } } /** * Analyze query execution plan */ analyzeQueryPlan(sql) { const cacheKey = sql; // Check cache first const cached = this.queryPlanCache.get(cacheKey); if (cached) return cached; try { const plan = this.db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(); const planText = JSON.stringify(plan); // Cache the plan this.queryPlanCache.set(cacheKey, planText); return planText; } catch (error) { return ''; } } /** * Check if query is eligible for caching */ checkCacheEligibility(query, intent) { // Don't cache if: // - Query modifies data // - Query uses time-sensitive functions // - Result set is expected to be very large const sql = query.sql.toLowerCase(); if (sql.includes('insert') || sql.includes('update') || sql.includes('delete')) { return false; } if (sql.includes('datetime(') || sql.includes('date(') || sql.includes('now(')) { return false; } // Check expected result size if (intent?.limit && intent.limit > 1000) { return false; } return true; } /** * Optimize column selection */ optimizeColumnSelection(query, intent) { const optimizations = []; let optimized = false; // If using SELECT *, try to optimize to specific columns if (query.sql.includes('SELECT *') && intent?.jsonPaths) { // Extract required columns from JSONPaths const requiredColumns = new Set(); intent.jsonPaths.forEach(path => { const parts = path.split('.'); if (parts.length > 0) { requiredColumns.add(parts[0]); } }); if (requiredColumns.size > 0 && requiredColumns.size < 10) { // Replace SELECT * with specific columns const columns = Array.from(requiredColumns).join(', '); const newSQL = query.sql.replace(/SELECT \*/i, `SELECT ${columns}`); optimizations.push({ type: 'column_elimination', description: `Reduced columns from * to ${requiredColumns.size} specific columns`, impact: 'medium' }); return { optimized: true, query: { ...query, sql: newSQL }, optimizations }; } } return { optimized: false, query, optimizations }; } /** * Optimize filters using indexes */ optimizeFilters(query, queryPlan) { const optimizations = []; let costReduction = 1.0; // Check if query is using indexes effectively const isUsingIndex = queryPlan.includes('USING INDEX'); if (!isUsingIndex) { // Try to rewrite query to use indexes const whereMatch = query.sql.match(/WHERE\s+(.+?)(?:GROUP|ORDER|LIMIT|$)/i); if (whereMatch) { const whereClause = whereMatch[1]; // Find tables in query const tableMatch = query.sql.match(/FROM\s+(\w+)/i); if (tableMatch) { const table = tableMatch[1]; const indexes = this.indexCache.get(table) || []; // Check if any index columns are in WHERE clause for (const index of indexes) { for (const column of index.columns) { if (whereClause.includes(column)) { optimizations.push({ type: 'index', description: `Query can use index ${index.name} on column ${column}`, impact: 'high' }); costReduction *= 0.5; // 50% cost reduction break; } } } } } } return { optimized: optimizations.length > 0, query, optimizations, costReduction }; } /** * Optimize join order based on table sizes */ optimizeJoins(query) { const optimizations = []; let costReduction = 1.0; // Extract joins from query const joinPattern = /JOIN\s+(\w+)\s+/gi; const joins = []; let match; while ((match = joinPattern.exec(query.sql)) !== null) { joins.push(match[1]); } if (joins.length > 1) { // Get table sizes const tableSizes = joins.map(table => ({ table, size: this.statsCache.get(table)?.rowCount || Number.MAX_SAFE_INTEGER })); // Sort by size (smallest first) tableSizes.sort((a, b) => a.size - b.size); // Check if current order is optimal const currentOrder = joins; const optimalOrder = tableSizes.map(t => t.table); if (JSON.stringify(currentOrder) !== JSON.stringify(optimalOrder)) { optimizations.push({ type: 'join_order', description: `Reorder joins for better performance: ${optimalOrder.join(' -> ')}`, impact: 'medium' }); costReduction *= 0.7; // 30% improvement } } return { optimized: optimizations.length > 0, query, optimizations, costReduction }; } /** * Add query hints for better execution */ addQueryHints(query, queryPlan) { const optimizations = []; let newSQL = query.sql; // Add INDEXED BY hints where beneficial if (!queryPlan.includes('SCAN TABLE') || !newSQL.includes('INDEXED BY')) { // Already using indexes efficiently return { optimized: false, query, optimizations }; } // Find table scans that could use indexes const scanMatch = queryPlan.match(/SCAN TABLE (\w+)/); if (scanMatch) { const table = scanMatch[1]; const indexes = this.indexCache.get(table) || []; if (indexes.length > 0) { // Use first available index as hint const indexHint = `${table} INDEXED BY ${indexes[0].name}`; newSQL = newSQL.replace(new RegExp(`\\b${table}\\b`), indexHint); optimizations.push({ type: 'index', description: `Added index hint for table ${table}`, impact: 'low' }); } } return { optimized: optimizations.length > 0, query: { ...query, sql: newSQL }, optimizations }; } /** * Clear all caches */ clearCache() { this.queryPlanCache.clear(); this.indexCache.clear(); this.statsCache.clear(); // Reload this.loadIndexInfo(); this.loadTableStats(); } } //# sourceMappingURL=QueryOptimizer.js.map