databaseManager->getConnection(); } /** * Discover all tables in the current database * * @return array */ public function discoverAllTables(): array { $driver = $this->getDriver(); if ($driver === 'pgsql') { return $this->discoverPostgreSQLTables(); } return $this->discoverMySQLTables(); } /** * Discover PostgreSQL tables */ private function discoverPostgreSQLTables(): array { $sql = " SELECT t.tablename as table_name, COALESCE(s.n_live_tup::bigint, 0) as estimated_rows, ROUND(pg_total_relation_size('public.'||t.tablename) / 1024.0 / 1024.0, 2) as size_mb, NULL as engine, NULL as table_collation FROM pg_tables t LEFT JOIN pg_stat_user_tables s ON s.relname = t.tablename WHERE t.schemaname = 'public' ORDER BY t.tablename "; $query = SqlQuery::create($sql, []); $result = $this->getConnection()->query($query); $tables = $result->fetchAll(); $tableMetadata = []; foreach ($tables as $tableData) { $estimatedRows = isset($tableData['estimated_rows']) && $tableData['estimated_rows'] !== null ? (int) $tableData['estimated_rows'] : 0; // For small tables (< 1000 estimated rows), get exact count // This ensures accuracy for small tables where pg_stat_user_tables might be outdated $rowCount = $estimatedRows; if ($estimatedRows < 1000) { try { $countQuery = SqlQuery::create("SELECT COUNT(*) FROM \"{$tableData['table_name']}\"", []); $exactCount = $this->getConnection()->queryScalar($countQuery); $rowCount = $exactCount !== null ? (int) $exactCount : $estimatedRows; } catch (\Throwable $e) { // If exact count fails, fall back to estimated count $rowCount = $estimatedRows; } } $tableMetadata[] = new TableMetadata( name: $tableData['table_name'], rowCount: $rowCount, sizeMb: isset($tableData['size_mb']) ? (float) $tableData['size_mb'] : null, engine: null, // PostgreSQL doesn't have engine concept collation: null, // PostgreSQL collation is per-column, not per-table ); } return $tableMetadata; } /** * Discover MySQL tables */ private function discoverMySQLTables(): array { $databaseName = $this->getCurrentDatabase(); $sql = " SELECT table_name, table_rows, ROUND((data_length + index_length) / 1024 / 1024, 2) as size_mb, engine, table_collation FROM information_schema.tables WHERE table_schema = ? AND table_type = 'BASE TABLE' ORDER BY table_name "; $query = SqlQuery::create($sql, [$databaseName]); $result = $this->getConnection()->query($query); $tables = $result->fetchAll(); $tableMetadata = []; foreach ($tables as $tableData) { $tableMetadata[] = new TableMetadata( name: $tableData['table_name'], rowCount: isset($tableData['table_rows']) ? (int) $tableData['table_rows'] : null, sizeMb: isset($tableData['size_mb']) ? (float) $tableData['size_mb'] : null, engine: $tableData['engine'] ?? null, collation: $tableData['table_collation'] ?? null, ); } return $tableMetadata; } /** * Discover a specific table by name */ public function discoverTable(string $tableName): ?TableMetadata { $driver = $this->getDriver(); if ($driver === 'pgsql') { return $this->discoverPostgreSQLTable($tableName); } return $this->discoverMySQLTable($tableName); } /** * Discover a specific PostgreSQL table */ private function discoverPostgreSQLTable(string $tableName): ?TableMetadata { $sql = " SELECT t.tablename as table_name, COALESCE(s.n_live_tup::bigint, 0) as table_rows, ROUND(pg_total_relation_size('public.'||?) / 1024.0 / 1024.0, 2) as size_mb, NULL as engine, NULL as table_collation FROM pg_tables t LEFT JOIN pg_stat_user_tables s ON s.relname = t.tablename WHERE t.schemaname = 'public' AND t.tablename = ? "; $query = SqlQuery::create($sql, [$tableName, $tableName]); $result = $this->getConnection()->query($query); $row = $result->fetch(); if ($row === null) { return null; } return new TableMetadata( name: $row['table_name'], rowCount: isset($row['table_rows']) && $row['table_rows'] !== null ? (int) $row['table_rows'] : null, sizeMb: isset($row['size_mb']) ? (float) $row['size_mb'] : null, engine: null, collation: null, ); } /** * Discover a specific MySQL table */ private function discoverMySQLTable(string $tableName): ?TableMetadata { $databaseName = $this->getCurrentDatabase(); $sql = " SELECT table_name, table_rows, ROUND((data_length + index_length) / 1024 / 1024, 2) as size_mb, engine, table_collation FROM information_schema.tables WHERE table_schema = ? AND table_name = ? "; $query = SqlQuery::create($sql, [$databaseName, $tableName]); $result = $this->getConnection()->query($query); $row = $result->fetch(); if ($row === null) { return null; } return new TableMetadata( name: $row['table_name'], rowCount: isset($row['table_rows']) ? (int) $row['table_rows'] : null, sizeMb: isset($row['size_mb']) ? (float) $row['size_mb'] : null, engine: $row['engine'] ?? null, collation: $row['table_collation'] ?? null, ); } /** * Get current database name */ private function getCurrentDatabase(): string { $driver = $this->getDriver(); if ($driver === 'pgsql') { $sql = "SELECT current_database() as db"; } else { $sql = "SELECT DATABASE() as db"; } $query = SqlQuery::create($sql, []); $result = $this->getConnection()->query($query); $row = $result->fetch(); return $row['db'] ?? 'unknown'; } /** * Get database driver name */ private function getDriver(): string { $pdo = $this->getConnection()->getPdo(); return $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); } }