diff --git a/src/Service/DatabaseService.php b/src/Service/DatabaseService.php index 2beb7b4..c3f34b4 100644 --- a/src/Service/DatabaseService.php +++ b/src/Service/DatabaseService.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Service; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; final class DatabaseService { @@ -28,7 +29,7 @@ final class DatabaseService ]; try { - // Check database exists + // Check database exists (cross-database query via system catalog) $exists = $this->connection->fetchOne( 'SELECT 1 FROM pg_database WHERE datname = :dbname', ['dbname' => $databaseName] @@ -38,33 +39,40 @@ final class DatabaseService return $fallback; } - // Database size + // Database size (cross-database, works from any connection) $sizeBytes = (int) $this->connection->fetchOne( 'SELECT pg_database_size(:dbname)', ['dbname' => $databaseName] ); $size = $this->formatBytes($sizeBytes); - // Table count - $tableCount = (int) $this->connection->fetchOne( - "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = :dbname", - ['dbname' => $databaseName] - ); - - // Active connections + // Active connections (cross-database system view) $activeConnections = (int) $this->connection->fetchOne( 'SELECT count(*) FROM pg_stat_activity WHERE datname = :dbname', ['dbname' => $databaseName] ); - // Cache hit ratio + // Cache hit ratio (cross-database system view) $cacheHitRatio = (float) ($this->connection->fetchOne( 'SELECT round(100.0 * sum(blks_hit) / nullif(sum(blks_hit + blks_read), 0), 2) FROM pg_stat_database WHERE datname = :dbname', ['dbname' => $databaseName] ) ?? 0); - // Largest table — requires querying the target database catalog - $largestTable = $this->fetchLargestTable($databaseName); + // Connect to the target database for table-specific queries + $targetConn = $this->connectToDatabase($databaseName); + + try { + // Table count (must query target database's information_schema) + $tableCount = (int) $targetConn->fetchOne( + "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = :dbname", + ['dbname' => $databaseName] + ); + + // Largest table (must query target database's pg_class) + $largestTable = $this->fetchLargestTable($targetConn); + } finally { + $targetConn->close(); + } return [ 'connected' => true, @@ -80,15 +88,22 @@ final class DatabaseService } } - private function fetchLargestTable(string $databaseName): string + private function connectToDatabase(string $databaseName): Connection + { + $params = $this->connection->getParams(); + $params['dbname'] = $databaseName; + + return DriverManager::getConnection($params); + } + + private function fetchLargestTable(Connection $conn): string { try { - $row = $this->connection->fetchAssociative( + $row = $conn->fetchAssociative( "SELECT relname, pg_total_relation_size(c.oid) as total_size FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind = 'r' - AND c.relowner = (SELECT oid FROM pg_roles WHERE rolname = current_user) ORDER BY pg_total_relation_size(c.oid) DESC LIMIT 1" );