*/ public function generateRecommendations(string $tableName): array { $recommendations = []; // Get slow queries affecting this table $slowQueries = $this->getSlowQueriesForTable($tableName); foreach ($slowQueries as $query) { $analysis = $this->analyzer->analyzeQuery($query['sql']); // If query uses filesort or temporary table, suggest index if ($analysis['using_filesort'] || $analysis['using_temporary']) { $recommendation = $this->analyzeQueryForIndex($query, $tableName, $analysis); if ($recommendation) { $recommendations[] = $recommendation; } } // If full table scan detected if ($analysis['key_type'] === 'ALL' || $analysis['key_type'] === 'scan') { $recommendation = $this->suggestIndexForTableScan($query, $tableName, $analysis); if ($recommendation) { $recommendations[] = $recommendation; } } } // Deduplicate and prioritize recommendations return $this->deduplicateRecommendations($recommendations); } /** * Suggest composite index based on WHERE and ORDER BY clauses */ private function analyzeQueryForIndex(array $query, string $tableName, array $analysis): ?IndexRecommendation { $whereColumns = $this->extractWhereColumns($query['sql']); $orderByColumns = $this->extractOrderByColumns($query['sql']); if (empty($whereColumns) && empty($orderByColumns)) { return null; } // Composite index: WHERE columns first, then ORDER BY columns $columns = array_unique(array_merge($whereColumns, $orderByColumns)); if (empty($columns)) { return null; } $estimatedSpeedup = $this->estimateSpeedup($analysis); $affectedQueries = $this->countAffectedQueries($columns, $tableName); return new IndexRecommendation( tableName: $tableName, columns: $columns, indexType: IndexType::BTREE, reason: $this->generateRecommendationReason($whereColumns, $orderByColumns, $analysis), priority: RecommendationPriority::fromMetrics($estimatedSpeedup, $affectedQueries), estimatedSpeedup: $estimatedSpeedup, affectedQueries: $affectedQueries ); } /** * Suggest index for full table scan */ private function suggestIndexForTableScan(array $query, string $tableName, array $analysis): ?IndexRecommendation { $whereColumns = $this->extractWhereColumns($query['sql']); if (empty($whereColumns)) { return null; } $estimatedSpeedup = max(10.0, $analysis['rows_examined'] / 100); $affectedQueries = $this->countAffectedQueries($whereColumns, $tableName); return new IndexRecommendation( tableName: $tableName, columns: $whereColumns, indexType: IndexType::BTREE, reason: "Full table scan detected ({$analysis['rows_examined']} rows examined)", priority: RecommendationPriority::fromMetrics($estimatedSpeedup, $affectedQueries), estimatedSpeedup: $estimatedSpeedup, affectedQueries: $affectedQueries ); } /** * Extract columns from WHERE clause */ private function extractWhereColumns(string $sql): array { $columns = []; // Simple regex-based extraction (can be improved with SQL parser) if (preg_match('/WHERE\s+(.*?)(?:ORDER BY|GROUP BY|LIMIT|$)/is', $sql, $matches)) { $whereClause = $matches[1]; // Extract column names from conditions like "column = value" or "column IN (...)" if (preg_match_all('/(\w+)\s*(?:=|IN|>|<|>=|<=|LIKE)/i', $whereClause, $columnMatches)) { $columns = $columnMatches[1]; } } return array_unique(array_filter($columns, fn($col) => !in_array(strtoupper($col), ['AND', 'OR', 'NOT']))); } /** * Extract columns from ORDER BY clause */ private function extractOrderByColumns(string $sql): array { $columns = []; if (preg_match('/ORDER BY\s+(.*?)(?:LIMIT|$)/is', $sql, $matches)) { $orderByClause = $matches[1]; // Extract column names, ignoring ASC/DESC if (preg_match_all('/(\w+)\s*(?:ASC|DESC)?/i', $orderByClause, $columnMatches)) { $columns = array_filter($columnMatches[1], fn($col) => $col !== 'ASC' && $col !== 'DESC'); } } return array_unique($columns); } /** * Estimate query speedup from adding index */ private function estimateSpeedup(array $analysis): float { $rowsExamined = $analysis['rows_examined']; if ($rowsExamined === 0) { return 1.0; } // Rough speedup estimation based on rows examined if ($rowsExamined > 100000) { return 20.0; } if ($rowsExamined > 10000) { return 10.0; } if ($rowsExamined > 1000) { return 5.0; } if ($rowsExamined > 100) { return 2.0; } return 1.5; } /** * Count queries that would benefit from this index */ private function countAffectedQueries(array $columns, string $tableName): int { // Simplified: count slow queries with these columns // In production, would analyze query log more thoroughly return count($this->getSlowQueriesForTable($tableName)); } /** * Generate human-readable recommendation reason */ private function generateRecommendationReason(array $whereColumns, array $orderByColumns, array $analysis): string { $reasons = []; if (!empty($whereColumns)) { $whereStr = implode(', ', $whereColumns); $reasons[] = "WHERE clause on columns: {$whereStr}"; } if (!empty($orderByColumns)) { $orderStr = implode(', ', $orderByColumns); $reasons[] = "ORDER BY columns: {$orderStr}"; } if ($analysis['using_filesort']) { $reasons[] = "Query uses filesort"; } if ($analysis['using_temporary']) { $reasons[] = "Query uses temporary table"; } return implode('; ', $reasons); } /** * Get slow queries affecting a specific table */ private function getSlowQueriesForTable(string $tableName): array { // This would integrate with SlowQueryDetector // For now, return empty array (would need slow query log access) return []; } /** * Deduplicate recommendations and keep highest priority */ private function deduplicateRecommendations(array $recommendations): array { $unique = []; foreach ($recommendations as $recommendation) { $key = $recommendation->tableName . ':' . implode(',', $recommendation->columns); if (!isset($unique[$key]) || $this->isPriorityHigher($recommendation->priority, $unique[$key]->priority)) { $unique[$key] = $recommendation; } } // Sort by priority (CRITICAL > HIGH > MEDIUM > LOW) usort($unique, function(IndexRecommendation $a, IndexRecommendation $b) { $priorityOrder = [ RecommendationPriority::CRITICAL->value => 4, RecommendationPriority::HIGH->value => 3, RecommendationPriority::MEDIUM->value => 2, RecommendationPriority::LOW->value => 1 ]; return ($priorityOrder[$b->priority->value] ?? 0) - ($priorityOrder[$a->priority->value] ?? 0); }); return array_values($unique); } /** * Compare priority levels */ private function isPriorityHigher(RecommendationPriority $priority1, RecommendationPriority $priority2): bool { $priorityOrder = [ RecommendationPriority::CRITICAL->value => 4, RecommendationPriority::HIGH->value => 3, RecommendationPriority::MEDIUM->value => 2, RecommendationPriority::LOW->value => 1 ]; return ($priorityOrder[$priority1->value] ?? 0) > ($priorityOrder[$priority2->value] ?? 0); } }