diff --git a/src/Model/Project.php b/src/Model/Project.php index d6f911fd8..d1da374d9 100644 --- a/src/Model/Project.php +++ b/src/Model/Project.php @@ -116,7 +116,8 @@ protected function getMetadata(): ?array */ public function exists(): bool { - return !empty($this->getDomain()); + return (!empty($this->getDomain())) + && $this->repository->checkReplication($this->getBasicInfo()['dbName']); } /** diff --git a/src/Repository/GlobalContribsRepository.php b/src/Repository/GlobalContribsRepository.php index 26b4ba9e9..3d0035a7b 100644 --- a/src/Repository/GlobalContribsRepository.php +++ b/src/Repository/GlobalContribsRepository.php @@ -60,6 +60,7 @@ public function globalEditCounts(User $user): ?array // Pre-populate all projects' metadata, to prevent each project call from fetching it. $this->caProject->getRepository()->getAll(); + $this->checkReplicationAllProjects(); // Compile the output. $out = []; @@ -117,6 +118,34 @@ protected function globalEditCountsFromCentralAuth(User $user): ?array return $this->setCache($cacheKey, $out); } + /** + * Get, slice by slice, the list of projects that are actually replicated. + * Takes about 0.5s per slice. + * @return bool[] Keyed by database name, all values are true. + */ + public function checkReplicationAllProjects(): array + { + $cacheKey = $this->getCacheKey("global_replication_check"); + if ($this->cache->hasItem($cacheKey)) { + return $this->cache->getItem($cacheKey)->get(); + } + $result = []; + $exists = true; + $i = 0; + $sql = "SELECT DISTINCT table_schema + FROM information_schema.tables"; + while ($exists) { + $i += 1; + try { + $queryResult = $this->executeProjectsQuery("s$i", $sql)->fetchFirstColumn(); + $result = array_merge($result, $queryResult); + } catch (\Throwable) { + $exists = false; + } + } + return $this->setCache($cacheKey, $result, 'PT1H'); + } + /** * Loop through the given dbNames and create Project objects for each. * @param array $dbNames diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php index 48ddfe5b4..3e61ffbe2 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/ProjectRepository.php @@ -241,6 +241,47 @@ public function getOne(string $project): ?array return $this->setCache($cacheKey, $basicInfo, 'PT1H'); } + /** + * Is this project actually replicated? Sometimes projets aren't, + * despite being listed in meta_p.wiki. See T322466. + * @param string $project Database name, without _p. + * @return bool + */ + public function checkReplication(string $project): bool + { + if ('' == $project) { + // This means we failed to getBasicInfo. Let's try and AGF. + // Plus, keeps tests from breaking down. + return true; + } + $cacheKey = $this->getCacheKey($project, "replication_check"); + if ($this->cache->hasItem($cacheKey)) { + return $this->cache->getItem($cacheKey)->get(); + } + // GlobalContribs preloads replication checks for *all* projects + $allProjectsCacheKey = $this->getCacheKey('', "global_replication_check"); + if ($this->cache->hasItem($allProjectsCacheKey)) { + $globalReplicationChecks = $this->cache->getItem($allProjectsCacheKey)->get(); + return array_key_exists($project, $globalReplicationChecks); + } + $dbList = $this->getDbList(); + if (!array_key_exists($project, $dbList)) { + $result = false; + } else { + $dbSlice = $dbList[$project]; + $sql = "SELECT 1 + FROM information_schema.tables + WHERE table_schema = :project + LIMIT 1"; + $queryResult = $this->executeProjectsQuery($dbSlice, $sql, [ + 'project' => $project . "_p", + ])->fetchAssociative(); + $result = (1 == count($queryResult)); + } + // Cache for 1h and return + return $this->setCache($cacheKey, $result, 'PT1H'); // feels long to me, but as long as getOne + } + /** * Get metadata about a project, including the 'dbName', 'url' and 'lang' * diff --git a/tests/Model/GlobalContribsTest.php b/tests/Model/GlobalContribsTest.php index 635eb2196..752968b8b 100644 --- a/tests/Model/GlobalContribsTest.php +++ b/tests/Model/GlobalContribsTest.php @@ -90,6 +90,8 @@ public function testGlobalEdits(): void 'dbName' => 'wiki1', 'url' => 'https://wiki1.example.org', ]); + $wiki1Repo->method('checkReplication') + ->willReturn(true); $wiki1 = new Project('wiki1'); $wiki1->setRepository($wiki1Repo); diff --git a/tests/TestAdapter.php b/tests/TestAdapter.php index 0331f7aa7..710fdd02a 100644 --- a/tests/TestAdapter.php +++ b/tests/TestAdapter.php @@ -32,6 +32,8 @@ public function getProjectRepo(): MockObject 'dbName' => 'test_wiki', 'lang' => 'en', ]); + $repo->method('checkReplication') + ->willReturn(true); return $repo; }