getOptimizer($connection); echo "Optimizing tables...\n"; $results = $optimizer->optimizeTables($table); $this->displayResults($results); return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error: {$e->getMessage()}\n"; return ExitCode::SOFTWARE_ERROR; } } /** * Analyze database tables * * @param string $connection The database connection to use * @param string|null $table The table to analyze, or null for all tables * @return ExitCode */ #[ConsoleCommand('db:analyze', 'Analyze database tables')] public function analyze(string $connection = 'default', ?string $table = null): ExitCode { try { $optimizer = $this->getOptimizer($connection); echo "Analyzing tables...\n"; $results = $optimizer->analyzeTables($table); $this->displayResults($results); return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error: {$e->getMessage()}\n"; return ExitCode::SOFTWARE_ERROR; } } /** * Check database tables * * @param string $connection The database connection to use * @param string|null $table The table to check, or null for all tables * @param bool $extended Whether to perform an extended check * @return ExitCode */ #[ConsoleCommand('db:check', 'Check database tables for errors')] public function check(string $connection = 'default', ?string $table = null, bool $extended = false): ExitCode { try { $optimizer = $this->getOptimizer($connection); echo "Checking tables...\n"; $results = $optimizer->checkTables($table, $extended); $this->displayResults($results); return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error: {$e->getMessage()}\n"; return ExitCode::SOFTWARE_ERROR; } } /** * Show table status * * @param string $connection The database connection to use * @param string|null $table The table to show status for, or null for all tables * @return ExitCode */ #[ConsoleCommand('db:status', 'Show database table status')] public function status(string $connection = 'default', ?string $table = null): ExitCode { try { $optimizer = $this->getOptimizer($connection); echo "Table status:\n"; $results = $optimizer->getTableStatus($table); foreach ($results as $tableName => $status) { echo "\nTable: {$tableName}\n"; echo str_repeat('-', 80) . "\n"; // Format the status information $this->displayTableStatus($status); } return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error: {$e->getMessage()}\n"; return ExitCode::SOFTWARE_ERROR; } } /** * Show index statistics * * @param string $connection The database connection to use * @param string|null $table The table to show index statistics for, or null for all tables * @return ExitCode */ #[ConsoleCommand('db:indexes', 'Show database index statistics')] public function indexes(string $connection = 'default', ?string $table = null): ExitCode { try { $optimizer = $this->getOptimizer($connection); echo "Index statistics:\n"; $results = $optimizer->getIndexStatistics($table); foreach ($results as $tableName => $indexes) { echo "\nTable: {$tableName}\n"; echo str_repeat('-', 80) . "\n"; if (empty($indexes)) { echo "No indexes found.\n"; continue; } foreach ($indexes as $indexName => $indexInfo) { echo " Index: {$indexName}\n"; // Format the index information $this->displayIndexInfo($indexInfo); echo "\n"; } } return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error: {$e->getMessage()}\n"; return ExitCode::SOFTWARE_ERROR; } } /** * Get the appropriate optimizer for the database connection * * @param string $connection The database connection name * @return DatabaseOptimizer The database optimizer * @throws \RuntimeException If the database type is not supported */ private function getOptimizer(string $connection): DatabaseOptimizer { $conn = $this->databaseManager->getConnection($connection); $driver = $conn->getPdo()->getAttribute(\PDO::ATTR_DRIVER_NAME); return match ($driver) { 'mysql' => new MySQLOptimizer($conn), 'pgsql' => new PostgreSQLOptimizer($conn), 'sqlite' => new SQLiteOptimizer($conn), default => throw new \RuntimeException("Unsupported database driver: {$driver}") }; } /** * Display the results of an operation * * @param array $results The results to display */ private function displayResults(array $results): void { foreach ($results as $name => $result) { echo " {$name}: {$result}\n"; } } /** * Display table status information * * @param array $status The table status information */ private function displayTableStatus(array $status): void { // Handle error case if (isset($status['error'])) { echo " Error: {$status['error']}\n"; return; } // Display basic information if (isset($status['row_count'])) { echo " Rows: " . number_format($status['row_count']) . "\n"; } if (isset($status['columns'])) { echo " Columns: {$status['columns']}\n"; } // Display size information if (isset($status['total_size'])) { echo " Total size: {$status['total_size']}\n"; } elseif (isset($status['Data_length']) && isset($status['Index_length'])) { $totalSize = $status['Data_length'] + $status['Index_length']; echo " Total size: " . $this->formatBytes($totalSize) . "\n"; echo " Data size: " . $this->formatBytes($status['Data_length']) . "\n"; echo " Index size: " . $this->formatBytes($status['Index_length']) . "\n"; } // Display engine information for MySQL if (isset($status['Engine'])) { echo " Engine: {$status['Engine']}\n"; } // Display row format for MySQL if (isset($status['Row_format'])) { echo " Row format: {$status['Row_format']}\n"; } // Display collation for MySQL if (isset($status['Collation'])) { echo " Collation: {$status['Collation']}\n"; } // Display index count if (isset($status['indexes']) && is_array($status['indexes'])) { echo " Indexes: " . count($status['indexes']) . "\n"; } // Display foreign key count if (isset($status['foreign_keys']) && is_array($status['foreign_keys'])) { echo " Foreign keys: " . count($status['foreign_keys']) . "\n"; } // Display last update time for MySQL if (isset($status['Update_time']) && $status['Update_time']) { echo " Last updated: {$status['Update_time']}\n"; } // Display auto increment value for MySQL if (isset($status['Auto_increment']) && $status['Auto_increment']) { echo " Auto increment: " . number_format($status['Auto_increment']) . "\n"; } // Display PostgreSQL-specific information if (isset($status['dead_rows']) && isset($status['row_count']) && $status['row_count'] > 0) { $deadRowPercentage = round(($status['dead_rows'] / $status['row_count']) * 100, 2); echo " Dead rows: {$status['dead_rows']} ({$deadRowPercentage}%)\n"; } if (isset($status['last_vacuum'])) { echo " Last vacuum: {$status['last_vacuum']}\n"; } if (isset($status['last_analyze'])) { echo " Last analyze: {$status['last_analyze']}\n"; } } /** * Display index information * * @param array $indexInfo The index information */ private function displayIndexInfo(array $indexInfo): void { // Handle error case if (isset($indexInfo['error'])) { echo " Error: {$indexInfo['error']}\n"; return; } // Display index type if (isset($indexInfo['type'])) { echo " Type: {$indexInfo['type']}\n"; } elseif (isset($indexInfo['index_type'])) { echo " Type: {$indexInfo['index_type']}\n"; } // Display uniqueness if (isset($indexInfo['unique'])) { echo " Unique: " . ($indexInfo['unique'] ? 'Yes' : 'No') . "\n"; } elseif (isset($indexInfo['is_unique'])) { echo " Unique: " . ($indexInfo['is_unique'] ? 'Yes' : 'No') . "\n"; } elseif (isset($indexInfo['Non_unique'])) { echo " Unique: " . ($indexInfo['Non_unique'] == 0 ? 'Yes' : 'No') . "\n"; } // Display primary key status if (isset($indexInfo['is_primary'])) { echo " Primary key: " . ($indexInfo['is_primary'] ? 'Yes' : 'No') . "\n"; } // Display columns if (isset($indexInfo['columns']) && is_array($indexInfo['columns'])) { echo " Columns: " . implode(', ', $indexInfo['columns']) . "\n"; } elseif (isset($indexInfo['Column_name'])) { echo " Column: {$indexInfo['Column_name']}\n"; } // Display cardinality if (isset($indexInfo['cardinality'])) { echo " Cardinality: " . number_format($indexInfo['cardinality']) . "\n"; } elseif (isset($indexInfo['Cardinality'])) { echo " Cardinality: " . number_format($indexInfo['Cardinality']) . "\n"; } // Display index size if (isset($indexInfo['index_size'])) { echo " Size: {$indexInfo['index_size']}\n"; } // Display PostgreSQL-specific information if (isset($indexInfo['index_scans'])) { echo " Scans: " . number_format($indexInfo['index_scans']) . "\n"; } if (isset($indexInfo['tuples_read']) && isset($indexInfo['tuples_fetched'])) { echo " Tuples read: " . number_format($indexInfo['tuples_read']) . "\n"; echo " Tuples fetched: " . number_format($indexInfo['tuples_fetched']) . "\n"; } } /** * Format bytes to a human-readable string * * @param int $bytes The number of bytes * @param int $precision The number of decimal places * @return string The formatted string */ private function formatBytes(int $bytes, int $precision = 2): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, $precision) . ' ' . $units[$pow]; } }