Merge "Add MessagesBi.php"
[lhc/web/wiklou.git] / includes / Storage / NameTableStore.php
index 3516ffe..6c7919d 100644 (file)
@@ -26,7 +26,7 @@ use WANObjectCache;
 use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
 
 /**
  * @author Addshore
@@ -34,7 +34,7 @@ use Wikimedia\Rdbms\LoadBalancer;
  */
 class NameTableStore {
 
-       /** @var LoadBalancer */
+       /** @var ILoadBalancer */
        private $loadBalancer;
 
        /** @var WANObjectCache */
@@ -64,8 +64,13 @@ class NameTableStore {
        private $insertCallback = null;
 
        /**
-        * @param LoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
-        * @param WANObjectCache $cache A cache manager for caching data
+        * @param ILoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
+        * @param WANObjectCache $cache A cache manager for caching data. This can be the local
+        *        wiki's default instance even if $wikiId refers to a different wiki, since
+        *        makeGlobalKey() is used to constructed a key that allows cached names from
+        *        the same database to be re-used between wikis. For example, enwiki and frwiki will
+        *        use the same cache keys for names from the wikidatawiki database, regardless
+        *        of the cache's default key space.
         * @param LoggerInterface $logger
         * @param string $table
         * @param string $idField
@@ -77,7 +82,7 @@ class NameTableStore {
         * This parameter was introduced in 1.32
         */
        public function __construct(
-               LoadBalancer $dbLoadBalancer,
+               ILoadBalancer $dbLoadBalancer,
                WANObjectCache $cache,
                LoggerInterface $logger,
                $table,
@@ -153,11 +158,13 @@ class NameTableStore {
                if ( $searchResult === false ) {
                        $id = $this->store( $name );
                        if ( $id === null ) {
-                               // RACE: $name was already in the db, probably just inserted, so load from master
-                               // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs
-                               $table = $this->loadTable(
-                                       $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT )
-                               );
+                               // RACE: $name was already in the db, probably just inserted, so load from master.
+                               // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs.
+                               // ...but not during unit tests, because we need the fake DB tables of the default
+                               // connection.
+                               $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+                               $table = $this->reloadMap( $connFlags );
+
                                $searchResult = array_search( $name, $table, true );
                                if ( $searchResult === false ) {
                                        // Insert failed due to IGNORE flag, but DB_MASTER didn't give us the data
@@ -166,14 +173,15 @@ class NameTableStore {
                                        $this->logger->error( $m );
                                        throw new NameTableAccessException( $m );
                                }
-                               $this->purgeWANCache(
-                                       function () {
-                                               $this->cache->reap( $this->getCacheKey(), INF );
-                                       }
-                               );
+                       } elseif ( isset( $table[$id] ) ) {
+                               throw new NameTableAccessException(
+                                       "Expected unused ID from database insert for '$name' "
+                                       . " into '{$this->table}', but ID $id is already associated with"
+                                       . " the name '{$table[$id]}'! This may indicate database corruption!" );
                        } else {
                                $table[$id] = $name;
                                $searchResult = $id;
+
                                // As store returned an ID we know we inserted so delete from WAN cache
                                $this->purgeWANCache(
                                        function () {
@@ -187,6 +195,31 @@ class NameTableStore {
                return $searchResult;
        }
 
+       /**
+        * Reloads the name table from the master database, and purges the WAN cache entry.
+        *
+        * @note This should only be called in situations where the local cache has been detected
+        * to be out of sync with the database. There should be no reason to call this method
+        * from outside the NameTabelStore during normal operation. This method may however be
+        * useful in unit tests.
+        *
+        * @param int $connFlags ILoadBalancer::CONN_XXX flags. Optional.
+        *
+        * @return \string[] The freshly reloaded name map
+        */
+       public function reloadMap( $connFlags = 0 ) {
+               $this->tableCache = $this->loadTable(
+                       $this->getDBConnection( DB_MASTER, $connFlags )
+               );
+               $this->purgeWANCache(
+                       function () {
+                               $this->cache->reap( $this->getCacheKey(), INF );
+                       }
+               );
+
+               return $this->tableCache;
+       }
+
        /**
         * Get the id of the given name.
         * If the name doesn't exist this will throw.