Merge "Drop wfRunHooks, deprecated since 1.25"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 11 Oct 2018 19:41:18 +0000 (19:41 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 11 Oct 2018 19:41:18 +0000 (19:41 +0000)
76 files changed:
RELEASE-NOTES-1.32
includes/ActorMigration.php
includes/Block.php
includes/DefaultSettings.php
includes/EditPage.php
includes/OrderedStreamingForkController.php
includes/Revision.php
includes/Revision/RevisionStore.php
includes/Storage/NameTableStoreFactory.php
includes/actions/InfoAction.php
includes/api/ApiBase.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryUserContribs.php
includes/cache/MessageCache.php
includes/cache/UserCache.php
includes/changes/RecentChange.php
includes/db/DatabaseOracle.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/fields/HTMLInfoField.php
includes/installer/CliInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/i18n/en.json
includes/installer/i18n/qqq.json
includes/jobqueue/jobs/ThumbnailRenderJob.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMssql.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
includes/logging/LogEntry.php
includes/page/WikiPage.php
includes/parser/ParserOutput.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevisionDeleteUser.php
includes/specials/SpecialLog.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/NewFilesPager.php
includes/user/User.php
maintenance/initEditCount.php
maintenance/install.php
maintenance/migrateActors.php
maintenance/populateLogSearch.php
maintenance/reassignEdits.php
maintenance/removeUnusedAccounts.php
maintenance/rollbackEdits.php
maintenance/update.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/ActorMigrationTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/Revision/RevisionQueryInfoTest.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/Storage/NameTableStoreFactoryTest.php
tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
tests/phpunit/includes/db/DatabaseTestHelper.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
tests/phpunit/includes/logging/DatabaseLogEntryTest.php
tests/phpunit/includes/page/PageArchiveTestBase.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/parser/ParserOutputTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/languages/LanguageConverterTest.php

index 6a005a0..70eb920 100644 (file)
@@ -40,6 +40,10 @@ production.
   old and the new schema, but reading the new schema, so Multi-Content Revisions
   (MCR) are now functional per default. The new default value of the setting is
   SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW.
+* $wgActorTableSchemaMigrationStage no longer accepts MIGRATION_WRITE_BOTH or
+  MIGRATION_WRITE_NEW. It instead uses SCHEMA_COMPAT_WRITE_BOTH |
+  SCHEMA_COMPAT_READ_OLD and SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+  for intermediate stages of migration.
 
 ==== Removed configuration ====
 * $wgEnableAPI and $wgEnableWriteAPI – These settings, deprecated in 1.31,
@@ -510,6 +514,11 @@ because of Phabricator reports.
   RevisionArchiveRecord, RevisionFactory, RevisionLookup, RevisionRecord,
   RevisionSlots, RevisionStore, RevisionStoreRecord, SlotRecord, and
   SuppressedDataException.
+* When using OOUI HTMLForm containing an 'info' field which uses the 'rawrow'
+  option, it is now deprecated to give its contents (the 'default' option)
+  as a string. They should be given as a OOUI\FieldLayout object instead.
+  Notably, this affects fields defined in the 'GetPreferences' hook, because
+  Special:Preferences uses an OOUI form now. (If possible, don't use 'rawrow'.)
 
 === Other changes in 1.32 ===
 * (T198811) The following tables have had their UNIQUE indexes turned into
index 51dfa60..0c33eb9 100644 (file)
@@ -34,6 +34,12 @@ use Wikimedia\Rdbms\IDatabase;
  */
 class ActorMigration {
 
+       /**
+        * Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage
+        * expects MIGRATION_* or SCHEMA_COMPAT_*
+        */
+       const MIGRATION_STAGE_SCHEMA_COMPAT = 1;
+
        /**
         * Define fields that use temporary tables for transitional purposes
         * @var array Keys are '$key', values are arrays with four fields:
@@ -74,11 +80,27 @@ class ActorMigration {
        /** @var array|null Cache for `self::getJoin()` */
        private $joinCache = null;
 
-       /** @var int One of the MIGRATION_* constants */
+       /** @var int Combination of SCHEMA_COMPAT_* constants */
        private $stage;
 
        /** @private */
        public function __construct( $stage ) {
+               if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a write mode' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a read mode' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_BOTH ) {
+                       throw new InvalidArgumentException( 'Cannot read both schemas' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_OLD ) && !( $stage & SCHEMA_COMPAT_WRITE_OLD ) ) {
+                       throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_NEW ) && !( $stage & SCHEMA_COMPAT_WRITE_NEW ) ) {
+                       throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
+               }
+
                $this->stage = $stage;
        }
 
@@ -96,7 +118,7 @@ class ActorMigration {
         * @return string
         */
        public function isAnon( $field ) {
-               return $this->stage === MIGRATION_NEW ? "$field IS NULL" : "$field = 0";
+               return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
        }
 
        /**
@@ -105,7 +127,7 @@ class ActorMigration {
         * @return string
         */
        public function isNotAnon( $field ) {
-               return $this->stage === MIGRATION_NEW ? "$field IS NOT NULL" : "$field != 0";
+               return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
        }
 
        /**
@@ -140,18 +162,16 @@ class ActorMigration {
 
                        list( $text, $actor ) = self::getFieldNames( $key );
 
-                       if ( $this->stage === MIGRATION_OLD ) {
+                       if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
                                $fields[$key] = $key;
                                $fields[$text] = $text;
                                $fields[$actor] = 'NULL';
                        } else {
-                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
-
                                if ( isset( self::$tempTables[$key] ) ) {
                                        $t = self::$tempTables[$key];
                                        $alias = "temp_$key";
                                        $tables[$alias] = $t['table'];
-                                       $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+                                       $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
                                        $joinField = "{$alias}.{$t['field']}";
                                } else {
                                        $joinField = $actor;
@@ -159,15 +179,10 @@ class ActorMigration {
 
                                $alias = "actor_$key";
                                $tables[$alias] = 'actor';
-                               $joins[$alias] = [ $join, "{$alias}.actor_id = {$joinField}" ];
+                               $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
 
-                               if ( $this->stage === MIGRATION_NEW ) {
-                                       $fields[$key] = "{$alias}.actor_user";
-                                       $fields[$text] = "{$alias}.actor_name";
-                               } else {
-                                       $fields[$key] = "COALESCE( {$alias}.actor_user, $key )";
-                                       $fields[$text] = "COALESCE( {$alias}.actor_name, $text )";
-                               }
+                               $fields[$key] = "{$alias}.actor_user";
+                               $fields[$text] = "{$alias}.actor_name";
                                $fields[$actor] = $joinField;
                        }
 
@@ -197,11 +212,11 @@ class ActorMigration {
 
                list( $text, $actor ) = self::getFieldNames( $key );
                $ret = [];
-               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $ret[$key] = $user->getId();
                        $ret[$text] = $user->getName();
                }
-               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
                        // We need to be able to assign an actor ID if none exists
                        if ( !$user instanceof User && !$user->getActorId() ) {
                                $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
@@ -233,11 +248,11 @@ class ActorMigration {
                list( $text, $actor ) = self::getFieldNames( $key );
                $ret = [];
                $callback = null;
-               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $ret[$key] = $user->getId();
                        $ret[$text] = $user->getName();
                }
-               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
                        // We need to be able to assign an actor ID if none exists
                        if ( !$user instanceof User && !$user->getActorId() ) {
                                $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
@@ -301,6 +316,8 @@ class ActorMigration {
         *   - orconds: (array[]) array of alternatives in case a union of multiple
         *     queries would be more efficient than a query with OR. May have keys
         *     'actor', 'userid', 'username'.
+        *     Since 1.32, this is guaranteed to contain just one alternative if
+        *     $users contains a single user.
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         *  All tables and joins are aliased, so `+` is safe to use.
         */
@@ -332,44 +349,26 @@ class ActorMigration {
                list( $text, $actor ) = self::getFieldNames( $key );
 
                // Combine data into conditions to be ORed together
-               $actorNotEmpty = [];
-               if ( $this->stage === MIGRATION_OLD ) {
-                       $actors = [];
-                       $actorEmpty = [];
-               } elseif ( isset( self::$tempTables[$key] ) ) {
-                       $t = self::$tempTables[$key];
-                       $alias = "temp_$key";
-                       $tables[$alias] = $t['table'];
-                       $joins[$alias] = [
-                               $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
-                               "{$alias}.{$t['pk']} = {$t['joinPK']}"
-                       ];
-                       $joinField = "{$alias}.{$t['field']}";
-                       $actorEmpty = [ $joinField => null ];
-                       if ( $this->stage !== MIGRATION_NEW ) {
-                               // Otherwise the resulting test can evaluate to NULL, and
-                               // NOT(NULL) is NULL rather than true.
-                               $actorNotEmpty = [ "$joinField IS NOT NULL" ];
+               if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
+                       if ( $actors ) {
+                               if ( isset( self::$tempTables[$key] ) ) {
+                                       $t = self::$tempTables[$key];
+                                       $alias = "temp_$key";
+                                       $tables[$alias] = $t['table'];
+                                       $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+                                       $joinField = "{$alias}.{$t['field']}";
+                               } else {
+                                       $joinField = $actor;
+                               }
+                               $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
                        }
                } else {
-                       $joinField = $actor;
-                       $actorEmpty = [ $joinField => 0 ];
-               }
-
-               if ( $actors ) {
-                       $conds['actor'] = $db->makeList(
-                               $actorNotEmpty + [ $joinField => $actors ], IDatabase::LIST_AND
-                       );
-               }
-               if ( $this->stage < MIGRATION_NEW && $ids ) {
-                       $conds['userid'] = $db->makeList(
-                               $actorEmpty + [ $key => $ids ], IDatabase::LIST_AND
-                       );
-               }
-               if ( $this->stage < MIGRATION_NEW && $names ) {
-                       $conds['username'] = $db->makeList(
-                               $actorEmpty + [ $text => $names ], IDatabase::LIST_AND
-                       );
+                       if ( $ids ) {
+                               $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
+                       }
+                       if ( $names ) {
+                               $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
+                       }
                }
 
                return [
index 913aeb9..bf8bad1 100644 (file)
@@ -208,13 +208,14 @@ class Block {
        public static function selectFields() {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->ipb_by or $row->ipb_by_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -224,7 +225,7 @@ class Block {
                        'ipb_address',
                        'ipb_by',
                        'ipb_by_text',
-                       'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : 'NULL',
+                       'ipb_by_actor' => 'NULL',
                        'ipb_timestamp',
                        'ipb_auto',
                        'ipb_anon_only',
index 4ae3fe5..4ed1707 100644 (file)
@@ -8984,10 +8984,21 @@ $wgMultiContentRevisionSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_
 
 /**
  * Actor table schema migration stage.
+ *
+ * Use the SCHEMA_COMPAT_XXX flags. Supported values:
+ * - SCHEMA_COMPAT_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+ * - SCHEMA_COMPAT_NEW
+ *
+ * Note that reading the old and new schema at the same time is not supported
+ * in 1.32, but was (with significant query performance issues) in 1.31.
+ *
  * @since 1.31
- * @var int One of the MIGRATION_* constants
+ * @since 1.32 changed allowed flags
+ * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
  */
-$wgActorTableSchemaMigrationStage = MIGRATION_OLD;
+$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_OLD;
 
 /**
  * Temporary option to disable the date picker from the Expiry Widget.
index 6b79538..7143c3f 100644 (file)
@@ -2991,7 +2991,7 @@ ERROR;
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
-                               $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+                               $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
                        }
                }
 
@@ -3466,7 +3466,7 @@ ERROR;
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
-                               $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+                               $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
                        }
                }
        }
index 11abc81..5424b13 100644 (file)
@@ -135,13 +135,16 @@ class OrderedStreamingForkController extends ForkController {
        protected function consumeNoFork() {
                while ( !feof( $this->input ) ) {
                        $data = fgets( $this->input );
-                       if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+                       if ( substr( $data, -1 ) === "\n" ) {
+                               // Strip any final new line used to delimit lines of input.
+                               // The last line of input might not have it, though.
                                $data = substr( $data, 0, -1 );
                        }
-                       if ( strlen( $data ) !== 0 ) {
-                               $result = call_user_func( $this->workCallback, $data );
-                               fwrite( $this->output, "$result\n" );
+                       if ( $data === '' ) {
+                               continue;
                        }
+                       $result = call_user_func( $this->workCallback, $data );
+                       fwrite( $this->output, "$result\n" );
                }
        }
 
@@ -163,12 +166,12 @@ class OrderedStreamingForkController extends ForkController {
                                        $this->updateAvailableSockets( $sockets, $used, $sockets ? 0 : 5 );
                                } while ( !$sockets );
                        }
-                       // Strip the trailing \n. The last line of a file might not have a trailing
-                       // \n though
-                       if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+                       if ( substr( $data, - 1 ) === "\n" ) {
+                               // Strip any final new line used to delimit lines of input.
+                               // The last line of input might not have it, though.
                                $data = substr( $data, 0, -1 );
                        }
-                       if ( strlen( $data ) === 0 ) {
+                       if ( $data === '' ) {
                                continue;
                        }
                        $socket = array_pop( $sockets );
index 5a6afd8..e8fe8bd 100644 (file)
@@ -318,12 +318,13 @@ class Revision implements IDBAccessObject {
                global $wgActorTableSchemaMigrationStage;
 
                wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's
                        // no way the join it's trying to do can work once the old fields
-                       // aren't being written anymore.
+                       // aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -352,13 +353,14 @@ class Revision implements IDBAccessObject {
                global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
                global $wgMultiContentRevisionSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->rev_user or $row->rev_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -411,13 +413,14 @@ class Revision implements IDBAccessObject {
                global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
                global $wgMultiContentRevisionSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->ar_user or $row->ar_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
index bef566d..6d3b72c 100644 (file)
@@ -2145,8 +2145,8 @@ class RevisionStore
                        return;
                }
 
-               // XXX: we really want the default database ID...
-               $storeWiki = $storeWiki ?: wfWikiID();
+               $storeWiki = $storeWiki ?: $this->loadBalancer->getLocalDomainID();
+               // @FIXME: when would getDomainID() be false here?
                $dbWiki = $dbWiki ?: wfWikiID();
 
                if ( $dbWiki === $storeWiki ) {
index 02ea9a7..ec364f9 100644 (file)
@@ -94,9 +94,10 @@ class NameTableStoreFactory {
                if ( !isset( $infos[$tableName] ) ) {
                        throw new \InvalidArgumentException( "Invalid table name \$tableName" );
                }
-               if ( $wiki === wfWikiID() ) {
+               if ( $wiki === $this->lbFactory->getLocalDomainID() ) {
                        $wiki = false;
                }
+
                if ( isset( $this->stores[$tableName][$wiki] ) ) {
                        return $this->stores[$tableName][$wiki];
                }
index 1cc4288..d014503 100644 (file)
@@ -739,27 +739,18 @@ class InfoAction extends FormlessAction {
                                $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
-                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                        $tables = [ 'revision_actor_temp' ];
                                        $field = 'revactor_actor';
                                        $pageField = 'revactor_page';
                                        $tsField = 'revactor_timestamp';
                                        $joins = [];
-                               } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                               } else {
                                        $tables = [ 'revision' ];
                                        $field = 'rev_user_text';
                                        $pageField = 'rev_page';
                                        $tsField = 'rev_timestamp';
                                        $joins = [];
-                               } else {
-                                       $tables = [ 'revision', 'revision_actor_temp', 'actor' ];
-                                       $field = 'COALESCE( actor_name, rev_user_text)';
-                                       $pageField = 'rev_page';
-                                       $tsField = 'rev_timestamp';
-                                       $joins = [
-                                               'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ],
-                                               'actor' => [ 'LEFT JOIN', 'revactor_actor = actor_id' ],
-                                       ];
                                }
 
                                $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
index ac07eeb..c2e37e0 100644 (file)
@@ -2247,7 +2247,7 @@ abstract class ApiBase extends ContextSource {
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $apiModule = $this;
-               Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ] );
+               Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ], '1.25' );
                $desc = self::escapeWikiText( $desc );
                if ( is_array( $desc ) ) {
                        $desc = implode( "\n", $desc );
@@ -2337,7 +2337,7 @@ abstract class ApiBase extends ContextSource {
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $apiModule = $this;
-               Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ] );
+               Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ], '1.25' );
 
                if ( !$desc ) {
                        $desc = [];
index 9652f81..7d5f6e2 100644 (file)
@@ -182,19 +182,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        // Actually count the actions using a subquery (T66505 and T66507)
                        $tables = [ 'recentchanges' ];
                        $joins = [];
-                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD ) {
                                $userCond = 'rc_user_text = user_name';
                        } else {
                                $tables[] = 'actor';
-                               $joins['actor'] = [
-                                       $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
-                                       'rc_actor = actor_id'
-                               ];
-                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
-                                       $userCond = 'actor_user = user_id';
-                               } else {
-                                       $userCond = 'actor_user = user_id OR (rc_actor = 0 AND rc_user_text = user_name)';
-                               }
+                               $joins['actor'] = [ 'JOIN', 'rc_actor = actor_id' ];
+                               $userCond = 'actor_user = user_id';
                        }
                        $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
                        $this->addFields( [
index 85f3e4b..642c9ac 100644 (file)
@@ -80,12 +80,13 @@ class ApiQueryContributors extends ApiQueryBase {
                $result = $this->getResult();
                $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
 
-               // For MIGRATION_NEW, target indexes on the revision_actor_temp table.
-               // Otherwise, revision is fine because it'll have to check all revision rows anyway.
-               $pageField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'revactor_page' : 'rev_page';
-               $idField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+               // For SCHEMA_COMPAT_READ_NEW, target indexes on the
+               // revision_actor_temp table, otherwise on the revision table.
+               $pageField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
+                       ? 'revactor_page' : 'rev_page';
+               $idField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
                        ? 'revactor_actor' : $revQuery['fields']['rev_user'];
-               $countField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+               $countField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
                        ? 'revactor_actor' : $revQuery['fields']['rev_user_text'];
 
                // First, count anons
index 36d4d34..f16f958 100644 (file)
@@ -102,10 +102,8 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                        $from = $fromName ? "$op= " . $dbSecondary->addQuotes( $fromName ) : false;
 
                                        // For the new schema, pull from the actor table. For the
-                                       // old, pull from rev_user. For migration a FULL [OUTER]
-                                       // JOIN would be what we want, except MySQL doesn't support
-                                       // that so we have to UNION instead.
-                                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                                       // old, pull from rev_user.
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                                $res = $dbSecondary->select(
                                                        'actor',
                                                        [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
@@ -113,7 +111,7 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                                        $fname,
                                                        [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
                                                );
-                                       } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                                       } else {
                                                $res = $dbSecondary->select(
                                                        'revision',
                                                        [ 'actor_id' => 'NULL', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
@@ -121,46 +119,6 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                                        $fname,
                                                        [ 'DISTINCT', 'ORDER BY' => [ "rev_user_text $sort" ], 'LIMIT' => $limit ]
                                                );
-                                       } else {
-                                               // There are three queries we have to combine to be sure of getting all results:
-                                               //  - actor table (any rows that have been migrated will have empty rev_user_text)
-                                               //  - revision+actor by user id
-                                               //  - revision+actor by name for anons
-                                               $options = $dbSecondary->unionSupportsOrderAndLimit()
-                                                       ? [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ] : [];
-                                               $subsql = [];
-                                               $subsql[] = $dbSecondary->selectSQLText(
-                                                       'actor',
-                                                       [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
-                                                       array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
-                                                       $fname,
-                                                       $options
-                                               );
-                                               $subsql[] = $dbSecondary->selectSQLText(
-                                                       [ 'revision', 'actor' ],
-                                                       [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
-                                                       array_merge(
-                                                               [ "rev_user_text$like", 'rev_user != 0' ],
-                                                               $from ? [ "rev_user_text $from" ] : []
-                                                       ),
-                                                       $fname,
-                                                       array_merge( [ 'DISTINCT' ], $options ),
-                                                       [ 'actor' => [ 'LEFT JOIN', 'rev_user = actor_user' ] ]
-                                               );
-                                               $subsql[] = $dbSecondary->selectSQLText(
-                                                       [ 'revision', 'actor' ],
-                                                       [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
-                                                       array_merge(
-                                                               [ "rev_user_text$like", 'rev_user = 0' ],
-                                                               $from ? [ "rev_user_text $from" ] : []
-                                                       ),
-                                                       $fname,
-                                                       array_merge( [ 'DISTINCT' ], $options ),
-                                                       [ 'actor' => [ 'LEFT JOIN', 'rev_user_text = actor_name' ] ]
-                                               );
-                                               $sql = $dbSecondary->unionQueries( $subsql, false ) . " ORDER BY user_name $sort";
-                                               $sql = $dbSecondary->limitResult( $sql, $limit );
-                                               $res = $dbSecondary->query( $sql, $fname );
                                        }
 
                                        $count = 0;
@@ -205,9 +163,8 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        }
 
                        // For the new schema, just select from the actor table. For the
-                       // old and transitional schemas, select from user and left join
-                       // actor if it exists.
-                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                       // old, select from user.
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                $res = $dbSecondary->select(
                                        'actor',
                                        [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
@@ -215,7 +172,7 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                        __METHOD__,
                                        [ 'ORDER BY' => "user_id $sort" ]
                                );
-                       } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                       } else {
                                $res = $dbSecondary->select(
                                        'user',
                                        [ 'actor_id' => 'NULL', 'user_id' => 'user_id', 'user_name' => 'user_name' ],
@@ -223,15 +180,6 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                        __METHOD__,
                                        [ 'ORDER BY' => "user_id $sort" ]
                                );
-                       } else {
-                               $res = $dbSecondary->select(
-                                       [ 'user', 'actor' ],
-                                       [ 'actor_id', 'user_id', 'user_name' ],
-                                       array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
-                                       __METHOD__,
-                                       [ 'ORDER BY' => "user_id $sort" ],
-                                       [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
-                               );
                        }
                        $userIter = UserArray::newFromResult( $res );
                        $batchSize = count( $ids );
@@ -278,9 +226,8 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        }
 
                        // For the new schema, just select from the actor table. For the
-                       // old and transitional schemas, select from user and left join
-                       // actor if it exists then merge in any unknown users (IPs and imports).
-                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                       // old, select from user then merge in any unknown users (IPs and imports).
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                $res = $dbSecondary->select(
                                        'actor',
                                        [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
@@ -290,23 +237,12 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                );
                                $userIter = UserArray::newFromResult( $res );
                        } else {
-                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
-                                       $res = $dbSecondary->select(
-                                               'user',
-                                               [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
-                                               array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
-                                               __METHOD__
-                                       );
-                               } else {
-                                       $res = $dbSecondary->select(
-                                               [ 'user', 'actor' ],
-                                               [ 'actor_id', 'user_id', 'user_name' ],
-                                               array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
-                                               __METHOD__,
-                                               [],
-                                               [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
-                                       );
-                               }
+                               $res = $dbSecondary->select(
+                                       'user',
+                                       [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
+                                       array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
+                                       __METHOD__
+                               );
                                foreach ( $res as $row ) {
                                        $names[$row->user_name] = $row;
                                }
@@ -328,17 +264,8 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        $batchSize = count( $names );
                }
 
-               // During migration, force ordering on the client side because we're
-               // having to combine multiple queries that would otherwise have
-               // different sort orders.
-               if ( $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_BOTH ||
-                       $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_NEW
-               ) {
-                       $batchSize = 1;
-               }
-
                // With the new schema, the DB query will order by actor so update $this->orderBy to match.
-               if ( $batchSize > 1 && $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+               if ( $batchSize > 1 && ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
                        $this->orderBy = 'actor';
                }
 
@@ -352,64 +279,22 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                $userIter->next();
                        }
 
-                       // Ugh. We have to run the query three times, once for each
-                       // possible 'orcond' from ActorMigration, and then merge them all
-                       // together in the proper order. And preserving the correct
-                       // $hookData for each one.
-                       // @todo When ActorMigration is removed, this can go back to a
-                       //  single prepare and select.
-                       $merged = [];
-                       foreach ( [ 'actor', 'userid', 'username' ] as $which ) {
-                               if ( $this->prepareQuery( $users, $limit - $count, $which ) ) {
-                                       $hookData = [];
-                                       $res = $this->select( __METHOD__, [], $hookData );
-                                       foreach ( $res as $row ) {
-                                               $merged[] = [ $row, &$hookData ];
-                                       }
-                               }
-                       }
-                       $neg = $this->params['dir'] == 'newer' ? 1 : -1;
-                       usort( $merged, function ( $a, $b ) use ( $neg, $batchSize ) {
-                               if ( $batchSize === 1 ) { // One user, can't be different
-                                       $ret = 0;
-                               } elseif ( $this->orderBy === 'id' ) {
-                                       $ret = $a[0]->rev_user <=> $b[0]->rev_user;
-                               } elseif ( $this->orderBy === 'name' ) {
-                                       $ret = strcmp( $a[0]->rev_user_text, $b[0]->rev_user_text );
-                               } else {
-                                       $ret = $a[0]->rev_actor <=> $b[0]->rev_actor;
-                               }
-
-                               if ( !$ret ) {
-                                       $ret = strcmp(
-                                               wfTimestamp( TS_MW, $a[0]->rev_timestamp ),
-                                               wfTimestamp( TS_MW, $b[0]->rev_timestamp )
-                                       );
-                               }
-
-                               if ( !$ret ) {
-                                       $ret = $a[0]->rev_id <=> $b[0]->rev_id;
-                               }
-
-                               return $neg * $ret;
-                       } );
-                       $merged = array_slice( $merged, 0, $limit - $count + 1 );
-                       // (end "Ugh")
+                       $hookData = [];
+                       $this->prepareQuery( $users, $limit - $count );
+                       $res = $this->select( __METHOD__, [], $hookData );
 
                        if ( $this->fld_sizediff ) {
                                $revIds = [];
-                               foreach ( $merged as $data ) {
-                                       if ( $data[0]->rev_parent_id ) {
-                                               $revIds[] = $data[0]->rev_parent_id;
+                               foreach ( $res as $row ) {
+                                       if ( $row->rev_parent_id ) {
+                                               $revIds[] = $row->rev_parent_id;
                                        }
                                }
                                $this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
                                        ->listRevisionSizes( $dbSecondary, $revIds );
                        }
 
-                       foreach ( $merged as $data ) {
-                               $row = $data[0];
-                               $hookData = &$data[1];
+                       foreach ( $res as $row ) {
                                if ( ++$count > $limit ) {
                                        // We've reached the one extra which shows that there are
                                        // additional pages to be had. Stop here...
@@ -434,10 +319,8 @@ class ApiQueryUserContribs extends ApiQueryBase {
         * Prepares the query and returns the limit of rows requested
         * @param User[] $users
         * @param int $limit
-        * @param string $which 'actor', 'userid', or 'username'
-        * @return bool
         */
-       private function prepareQuery( array $users, $limit, $which ) {
+       private function prepareQuery( array $users, $limit ) {
                global $wgActorTableSchemaMigrationStage, $wgChangeTagsSchemaMigrationStage;
 
                $this->resetQueryParams();
@@ -448,27 +331,26 @@ class ApiQueryUserContribs extends ApiQueryBase {
                $this->addJoinConds( $revQuery['joins'] );
                $this->addFields( $revQuery['fields'] );
 
-               $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
-               if ( !isset( $revWhere['orconds'][$which] ) ) {
-                       return false;
-               }
-               $this->addWhere( $revWhere['orconds'][$which] );
-
-               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
+                       $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
                        $orderUserField = 'rev_actor';
                        $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
-               } else {
-                       $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
-                       $userField = $revQuery['fields'][$orderUserField];
-               }
-               if ( $which === 'actor' ) {
                        $tsField = 'revactor_timestamp';
                        $idField = 'revactor_rev';
                } else {
+                       // If we're dealing with user names (rather than IDs) in read-old mode,
+                       // pass false for ActorMigration::getWhere()'s $useId parameter so
+                       // $revWhere['conds'] isn't an OR.
+                       $revWhere = ActorMigration::newMigration()
+                               ->getWhere( $db, 'rev_user', $users, $this->orderBy === 'id' );
+                       $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
+                       $userField = $revQuery['fields'][$orderUserField];
                        $tsField = 'rev_timestamp';
                        $idField = 'rev_id';
                }
 
+               $this->addWhere( $revWhere['conds'] );
+
                // Handle continue parameter
                if ( !is_null( $this->params['continue'] ) ) {
                        $continue = explode( '|', $this->params['continue'] );
@@ -620,8 +502,6 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                $this->addWhereFld( 'ct_tag', $this->params['tag'] );
                        }
                }
-
-               return true;
        }
 
        /**
index 76d31ff..733b1b7 100644 (file)
@@ -464,13 +464,7 @@ class MessageCache {
 
                $cache = [];
 
-               # Common conditions
-               $conds = [
-                       'page_is_redirect' => 0,
-                       'page_namespace' => NS_MEDIAWIKI,
-               ];
-
-               $mostused = [];
+               $mostused = []; // list of "<cased message key>/<code>"
                if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
                        if ( !$this->cache->has( $wgLanguageCode ) ) {
                                $this->load( $wgLanguageCode );
@@ -481,6 +475,14 @@ class MessageCache {
                        }
                }
 
+               // Get the list of software-defined messages in core/extensions
+               $overridable = array_flip( Language::getMessageKeysFor( $wgLanguageCode ) );
+
+               // Common conditions
+               $conds = [
+                       'page_is_redirect' => 0,
+                       'page_namespace' => NS_MEDIAWIKI,
+               ];
                if ( count( $mostused ) ) {
                        $conds['page_title'] = $mostused;
                } elseif ( $code !== $wgLanguageCode ) {
@@ -492,31 +494,28 @@ class MessageCache {
                                $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
                }
 
-               # Conditions to fetch oversized pages to ignore them
-               $bigConds = $conds;
-               $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
-
-               # Load titles for all oversized pages in the MediaWiki namespace
+               // Set the stubs for oversized software-defined messages in the main cache map
                $res = $dbr->select(
                        'page',
                        [ 'page_title', 'page_latest' ],
-                       $bigConds,
+                       array_merge( $conds, [ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-big"
                );
                foreach ( $res as $row ) {
-                       $cache[$row->page_title] = '!TOO BIG';
+                       $name = $this->contLang->lcfirst( $row->page_title );
+                       // Include entries/stubs for all keys in $mostused in adaptive mode
+                       if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+                               $cache[$row->page_title] = '!TOO BIG';
+                       }
                        // At least include revision ID so page changes are reflected in the hash
                        $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                }
 
-               # Conditions to load the remaining pages with their contents
-               $smallConds = $conds;
-               $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
-
+               // Set the text for small software-defined messages in the main cache map
                $res = $dbr->select(
                        [ 'page', 'revision', 'text' ],
-                       [ 'page_title', 'old_id', 'old_text', 'old_flags' ],
-                       $smallConds,
+                       [ 'page_title', 'page_latest', 'old_id', 'old_text', 'old_flags' ],
+                       array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-small",
                        [],
                        [
@@ -524,23 +523,30 @@ class MessageCache {
                                'text' => [ 'JOIN', 'rev_text_id=old_id' ],
                        ]
                );
-
                foreach ( $res as $row ) {
-                       $text = Revision::getRevisionText( $row );
-                       if ( $text === false ) {
-                               // Failed to fetch data; possible ES errors?
-                               // Store a marker to fetch on-demand as a workaround...
-                               // TODO Use a differnt marker
-                               $entry = '!TOO BIG';
-                               wfDebugLog(
-                                       'MessageCache',
-                                       __METHOD__
-                                       . ": failed to load message page text for {$row->page_title} ($code)"
-                               );
+                       $name = $this->contLang->lcfirst( $row->page_title );
+                       // Include entries/stubs for all keys in $mostused in adaptive mode
+                       if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+                               $text = Revision::getRevisionText( $row );
+                               if ( $text === false ) {
+                                       // Failed to fetch data; possible ES errors?
+                                       // Store a marker to fetch on-demand as a workaround...
+                                       // TODO Use a differnt marker
+                                       $entry = '!TOO BIG';
+                                       wfDebugLog(
+                                               'MessageCache',
+                                               __METHOD__
+                                               . ": failed to load message page text for {$row->page_title} ($code)"
+                                       );
+                               } else {
+                                       $entry = ' ' . $text;
+                               }
+                               $cache[$row->page_title] = $entry;
                        } else {
-                               $entry = ' ' . $text;
+                               // T193271: cache object gets too big and slow to generate.
+                               // At least include revision ID so page changes are reflected in the hash.
+                               $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                        }
-                       $cache[$row->page_title] = $entry;
                }
 
                $cache['VERSION'] = MSG_CACHE_VERSION;
@@ -818,9 +824,8 @@ class MessageCache {
                Hooks::run( 'MessageCache::get', [ &$lckey ] );
 
                // Loop through each language in the fallback list until we find something useful
-               $lang = wfGetLangObj( $langcode );
                $message = $this->getMessageFromFallbackChain(
-                       $lang,
+                       wfGetLangObj( $langcode ),
                        $lckey,
                        !$this->mDisable && $useDB
                );
@@ -912,7 +917,6 @@ class MessageCache {
                                        $this->getMessagePageName( $langcode, $uckey ),
                                        $langcode
                                );
-
                                if ( $message !== false ) {
                                        return $message;
                                }
@@ -987,44 +991,54 @@ class MessageCache {
                $this->load( $code );
 
                $entry = $this->cache->getField( $code, $title );
+
                if ( $entry !== null ) {
+                       // Message page exists as an override of a software messages
                        if ( substr( $entry, 0, 1 ) === ' ' ) {
                                // The message exists and is not '!TOO BIG'
                                return (string)substr( $entry, 1 );
                        } elseif ( $entry === '!NONEXISTENT' ) {
+                               // The text might be '-' or missing due to some data loss
                                return false;
                        }
-                       // Fall through and try invididual message cache below
-               } else {
-                       // Message does not have a MediaWiki page definition
-                       $message = false;
-                       Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
-                       if ( $message !== false ) {
-                               $this->cache->setField( $code, $title, ' ' . $message );
-                       } else {
-                               $this->cache->setField( $code, $title, '!NONEXISTENT' );
-                       }
-
-                       return $message;
-               }
-
-               if ( $this->cacheVolatile[$code] ) {
-                       $entry = false;
-                       // Make sure that individual keys respect the WAN cache holdoff period too
-                       LoggerFactory::getInstance( 'MessageCache' )->debug(
-                               __METHOD__ . ': loading volatile key \'{titleKey}\'',
-                               [ 'titleKey' => $title, 'code' => $code ] );
+                       // Load the message page, utilizing the individual message cache.
+                       // If the page does not exist, there will be no hook handler fallbacks.
+                       $entry = $this->loadCachedMessagePageEntry(
+                               $title,
+                               $code,
+                               $this->cache->getField( $code, 'HASH' )
+                       );
                } else {
-                       // Try the individual message cache
+                       // Message page does not exist or does not override a software message.
+                       // Load the message page, utilizing the individual message cache.
                        $entry = $this->loadCachedMessagePageEntry(
                                $title,
                                $code,
                                $this->cache->getField( $code, 'HASH' )
                        );
+                       if ( substr( $entry, 0, 1 ) !== ' ' ) {
+                               // Message does not have a MediaWiki page definition; try hook handlers
+                               $message = false;
+                               Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
+                               if ( $message !== false ) {
+                                       $this->cache->setField( $code, $title, ' ' . $message );
+                               } else {
+                                       $this->cache->setField( $code, $title, '!NONEXISTENT' );
+                               }
+
+                               return $message;
+                       }
                }
 
                if ( $entry !== false && substr( $entry, 0, 1 ) === ' ' ) {
-                       $this->cache->setField( $code, $title, $entry );
+                       if ( $this->cacheVolatile[$code] ) {
+                               // Make sure that individual keys respect the WAN cache holdoff period too
+                               LoggerFactory::getInstance( 'MessageCache' )->debug(
+                                       __METHOD__ . ': loading volatile key \'{titleKey}\'',
+                                       [ 'titleKey' => $title, 'code' => $code ] );
+                       } else {
+                               $this->cache->setField( $code, $title, $entry );
+                       }
                        // The message exists, so make sure a string is returned
                        return (string)substr( $entry, 1 );
                }
index 2828b9a..8f816d9 100644 (file)
@@ -105,11 +105,13 @@ class UserCache {
                        $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
                        $joinConds = [];
 
-                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+                       // but it does little harm and might be needed for write callers loading a User.
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
                                $tables[] = 'actor';
                                $fields[] = 'actor_id';
                                $joinConds['actor'] = [
-                                       $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                       ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
                                        [ 'actor_user = user_id' ]
                                ];
                        }
@@ -125,7 +127,7 @@ class UserCache {
                                $this->cache[$userId]['name'] = $row->user_name;
                                $this->cache[$userId]['real_name'] = $row->user_real_name;
                                $this->cache[$userId]['registration'] = $row->user_registration;
-                               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
                                        $this->cache[$userId]['actor'] = $row->actor_id;
                                }
                                $usersToCheck[$userId] = $row->user_name;
index 819f170..7f7d77d 100644 (file)
@@ -228,13 +228,14 @@ class RecentChange {
                global $wgActorTableSchemaMigrationStage;
 
                wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->rc_user or $row->rc_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
index 876b9bb..6d921b9 100644 (file)
 
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\Blob;
 use Wikimedia\Rdbms\ResultWrapper;
 use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\DBUnexpectedError;
+use Wikimedia\Rdbms\DBExpectedError;
 
 /**
  * @ingroup Database
@@ -81,8 +83,9 @@ class DatabaseOracle extends Database {
                return false;
        }
 
-       function open( $server, $user, $password, $dbName ) {
+       protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
                global $wgDBOracleDRCP;
+
                if ( !function_exists( 'oci_connect' ) ) {
                        throw new DBConnectionError(
                                $this,
@@ -94,20 +97,15 @@ class DatabaseOracle extends Database {
                $this->close();
                $this->user = $user;
                $this->password = $password;
-               // changed internal variables functions
-               // mServer now holds the TNS endpoint
-               // mDBname is schema name if different from username
                if ( !$server ) {
-                       // backward compatibillity (server used to be null and TNS was supplied in dbname)
+                       // Backward compatibility (server used to be null and TNS was supplied in dbname)
                        $this->server = $dbName;
-                       $this->dbName = $user;
+                       $realDatabase = $user;
                } else {
+                       // $server now holds the TNS endpoint
                        $this->server = $server;
-                       if ( !$dbName ) {
-                               $this->dbName = $user;
-                       } else {
-                               $this->dbName = $dbName;
-                       }
+                       // $dbName is schema name if different from username
+                       $realDatabase = $dbName ?: $user;
                }
 
                if ( !strlen( $user ) ) { # e.g. the class is being loaded
@@ -148,9 +146,15 @@ class DatabaseOracle extends Database {
                }
                Wikimedia\restoreWarnings();
 
-               if ( $this->user != $this->dbName ) {
+               if ( $this->user != $realDatabase ) {
                        // change current schema in session
-                       $this->selectDB( $this->dbName );
+                       $this->selectDB( $realDatabase );
+               } else {
+                       $this->currentDomain = new DatabaseDomain(
+                               $realDatabase,
+                               null,
+                               $tablePrefix
+                       );
                }
 
                if ( !$this->conn ) {
@@ -654,8 +658,8 @@ class DatabaseOracle extends Database {
                                        atc.table_name
                                ) || '_' ||
                                atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
-                               AND asq.sequence_owner = upper('{$this->dbName}')
-                               AND atc.owner = upper('{$this->dbName}')" );
+                               AND asq.sequence_owner = upper('{$this->getDBname()}')
+                               AND atc.owner = upper('{$this->getDBname()}')" );
 
                        while ( ( $row = $result->fetchRow() ) !== false ) {
                                $this->sequenceData[$row[1]] = [
@@ -735,7 +739,7 @@ class DatabaseOracle extends Database {
                        $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
                }
 
-               $owner = strtoupper( $this->dbName );
+               $owner = strtoupper( $this->getDBname() );
                $result = $this->doQuery( "SELECT table_name FROM all_tables " .
                        "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
 
@@ -813,7 +817,7 @@ class DatabaseOracle extends Database {
                $table = $this->tableName( $table );
                $table = strtoupper( $this->removeIdentifierQuotes( $table ) );
                $index = strtoupper( $index );
-               $owner = strtoupper( $this->dbName );
+               $owner = strtoupper( $this->getDBname() );
                $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
                $res = $this->doQuery( $sql );
                if ( $res ) {
@@ -835,7 +839,7 @@ class DatabaseOracle extends Database {
        function tableExists( $table, $fname = __METHOD__ ) {
                $table = $this->tableName( $table );
                $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
-               $owner = $this->addQuotes( strtoupper( $this->dbName ) );
+               $owner = $this->addQuotes( strtoupper( $this->getDBname() ) );
                $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
                $res = $this->doQuery( $sql );
                if ( $res && $res->numRows() > 0 ) {
@@ -1031,23 +1035,33 @@ class DatabaseOracle extends Database {
                return true;
        }
 
-       function selectDB( $db ) {
-               $this->dbName = $db;
-               if ( $db == null || $db == $this->user ) {
+       protected function doSelectDomain( DatabaseDomain $domain ) {
+               if ( $domain->getSchema() !== null ) {
+                       // We use the *database* aspect of $domain for schema, not the domain schema
+                       throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+               }
+
+               $database = $domain->getDatabase();
+               if ( $database === null || $database === $this->user ) {
+                       // Backward compatibility
+                       $this->currentDomain = $domain;
+
                        return true;
                }
-               $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
+
+               // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj32268.html
+               $encDatabase = $this->addIdentifierQuotes( strtoupper( $database ) );
+               $sql = "ALTER SESSION SET CURRENT_SCHEMA=$encDatabase";
                $stmt = oci_parse( $this->conn, $sql );
                Wikimedia\suppressWarnings();
                $success = oci_execute( $stmt );
                Wikimedia\restoreWarnings();
-               if ( !$success ) {
+               if ( $success ) {
+                       // Update that domain fields on success (no exception thrown)
+                       $this->currentDomain = $domain;
+               } else {
                        $e = oci_error( $stmt );
-                       if ( $e['code'] != '1435' ) {
-                               $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-                       }
-
-                       return false;
+                       $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
                }
 
                return true;
@@ -1179,7 +1193,9 @@ class DatabaseOracle extends Database {
                // all deletions on these tables have transactions so final failure rollbacks these updates
                // @todo: Normalize the schema to match MySQL, no special FKs and such
                $table = $this->tableName( $table );
-               if ( $table == $this->tableName( 'user' ) && $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+               if ( $table == $this->tableName( 'user' ) &&
+                       ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD )
+               ) {
                        $this->update( 'archive', [ 'ar_user' => 0 ],
                                [ 'ar_user' => $conds['user_id'] ], $fname );
                        $this->update( 'ipblocks', [ 'ipb_user' => 0 ],
@@ -1332,10 +1348,6 @@ class DatabaseOracle extends Database {
                return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
        }
 
-       function getDBname() {
-               return $this->dbName;
-       }
-
        function getServer() {
                return $this->server;
        }
index 5589c68..4a84cff 100644 (file)
@@ -221,13 +221,14 @@ class ArchivedFile {
        static function selectFields() {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->fa_user or $row->fa_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -248,7 +249,7 @@ class ArchivedFile {
                        'fa_minor_mime',
                        'fa_user',
                        'fa_user_text',
-                       'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : 'NULL',
+                       'fa_actor' => 'NULL',
                        'fa_timestamp',
                        'fa_deleted',
                        'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
index ec01869..254ceff 100644 (file)
@@ -201,13 +201,14 @@ class LocalFile extends File {
                global $wgActorTableSchemaMigrationStage;
 
                wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->img_user or $row->img_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -223,7 +224,7 @@ class LocalFile extends File {
                        'img_minor_mime',
                        'img_user',
                        'img_user_text',
-                       'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : 'NULL',
+                       'img_actor' => 'NULL',
                        'img_timestamp',
                        'img_sha1',
                ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
@@ -1573,16 +1574,16 @@ class LocalFile extends File {
                                }
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $fields['oi_user'] = 'img_user';
                                $fields['oi_user_text'] = 'img_user_text';
                        }
-                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $fields['oi_actor'] = 'img_actor';
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
-                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       if (
+                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
                        ) {
                                // Upgrade any rows that are still old-style. Otherwise an upgrade
                                // might be missed if a deletion happens while the migration script
@@ -2569,16 +2570,16 @@ class LocalFileDeleteBatch {
                                }
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $fields['fa_user'] = 'img_user';
                                $fields['fa_user_text'] = 'img_user_text';
                        }
-                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $fields['fa_actor'] = 'img_actor';
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
-                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       if (
+                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
                        ) {
                                // Upgrade any rows that are still old-style. Otherwise an upgrade
                                // might be missed if a deletion happens while the migration script
index 9759b79..f103afa 100644 (file)
@@ -115,13 +115,14 @@ class OldLocalFile extends LocalFile {
                global $wgActorTableSchemaMigrationStage;
 
                wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                        // If code is using this instead of self::getQueryInfo(), there's a
                        // decent chance it's going to try to directly access
                        // $row->oi_user or $row->oi_user_text and we can't give it
-                       // useful values here once those aren't being written anymore.
+                       // useful values here once those aren't being used anymore.
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
                        );
                }
 
@@ -138,7 +139,7 @@ class OldLocalFile extends LocalFile {
                        'oi_minor_mime',
                        'oi_user',
                        'oi_user_text',
-                       'oi_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'oi_actor' : 'NULL',
+                       'oi_actor' => 'NULL',
                        'oi_timestamp',
                        'oi_deleted',
                        'oi_sha1',
index a98f112..c0dbf50 100644 (file)
@@ -83,7 +83,8 @@ class HTMLInfoField extends HTMLFormField {
        public function getOOUI( $value ) {
                if ( !empty( $this->mParams['rawrow'] ) ) {
                        if ( !( $value instanceof OOUI\FieldLayout ) ) {
-                               throw new Exception( "'default' must be a FieldLayout or subclass when using 'rawrow'" );
+                               wfDeprecated( "'default' parameter as a string when using 'rawrow' " .
+                                       "(must be a FieldLayout or subclass)", '1.32' );
                        }
                        return $value;
                }
index aee51e7..f59b5da 100644 (file)
@@ -50,30 +50,30 @@ class CliInstaller extends Installer {
        /**
         * @param string $siteName
         * @param string|null $admin
-        * @param array $option
+        * @param array $options
         */
-       function __construct( $siteName, $admin = null, array $option = [] ) {
+       function __construct( $siteName, $admin = null, array $options = [] ) {
                global $wgContLang;
 
                parent::__construct();
 
-               if ( isset( $option['scriptpath'] ) ) {
+               if ( isset( $options['scriptpath'] ) ) {
                        $this->specifiedScriptPath = true;
                }
 
                foreach ( $this->optionMap as $opt => $global ) {
-                       if ( isset( $option[$opt] ) ) {
-                               $GLOBALS[$global] = $option[$opt];
-                               $this->setVar( $global, $option[$opt] );
+                       if ( isset( $options[$opt] ) ) {
+                               $GLOBALS[$global] = $options[$opt];
+                               $this->setVar( $global, $options[$opt] );
                        }
                }
 
-               if ( isset( $option['lang'] ) ) {
+               if ( isset( $options['lang'] ) ) {
                        global $wgLang, $wgLanguageCode;
-                       $this->setVar( '_UserLang', $option['lang'] );
-                       $wgLanguageCode = $option['lang'];
+                       $this->setVar( '_UserLang', $options['lang'] );
+                       $wgLanguageCode = $options['lang'];
                        $wgContLang = MediaWikiServices::getInstance()->getContentLanguage();
-                       $wgLang = Language::factory( $option['lang'] );
+                       $wgLang = Language::factory( $options['lang'] );
                        RequestContext::getMain()->setLanguage( $wgLang );
                }
 
@@ -89,32 +89,47 @@ class CliInstaller extends Installer {
                        $this->setVar( '_AdminName', $admin );
                }
 
-               if ( !isset( $option['installdbuser'] ) ) {
+               if ( !isset( $options['installdbuser'] ) ) {
                        $this->setVar( '_InstallUser',
                                $this->getVar( 'wgDBuser' ) );
                        $this->setVar( '_InstallPassword',
                                $this->getVar( 'wgDBpassword' ) );
                } else {
                        $this->setVar( '_InstallUser',
-                               $option['installdbuser'] );
+                               $options['installdbuser'] );
                        $this->setVar( '_InstallPassword',
-                               $option['installdbpass'] ?? "" );
+                               $options['installdbpass'] ?? "" );
 
                        // Assume that if we're given the installer user, we'll create the account.
                        $this->setVar( '_CreateDBAccount', true );
                }
 
-               if ( isset( $option['pass'] ) ) {
-                       $this->setVar( '_AdminPassword', $option['pass'] );
+               if ( isset( $options['pass'] ) ) {
+                       $this->setVar( '_AdminPassword', $options['pass'] );
                }
 
                // Detect and inject any extension found
-               if ( isset( $option['with-extensions'] ) ) {
+               if ( isset( $options['extensions'] ) ) {
+                       $status = $this->validateExtensions(
+                               'extension', 'extensions', $options['extensions'] );
+                       if ( !$status->isOK() ) {
+                               $this->showStatusMessage( $status );
+                       }
+                       $this->setVar( '_Extensions', $status->value );
+               } elseif ( isset( $options['with-extensions'] ) ) {
                        $this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
                }
 
                // Set up the default skins
-               $skins = array_keys( $this->findExtensions( 'skins' ) );
+               if ( isset( $options['skins'] ) ) {
+                       $status = $this->validateExtensions( 'skin', 'skins', $options['skins'] );
+                       if ( !$status->isOK() ) {
+                               $this->showStatusMessage( $status );
+                       }
+                       $skins = $status->value;
+               } else {
+                       $skins = array_keys( $this->findExtensions( 'skins' ) );
+               }
                $this->setVar( '_Skins', $skins );
 
                if ( $skins ) {
@@ -123,6 +138,28 @@ class CliInstaller extends Installer {
                }
        }
 
+       private function validateExtensions( $type, $directory, $nameLists ) {
+               $extensions = [];
+               $status = new Status;
+               foreach ( (array)$nameLists as $nameList ) {
+                       foreach ( explode( ',', $nameList ) as $name ) {
+                               $name = trim( $name );
+                               if ( $name === '' ) {
+                                       continue;
+                               }
+                               $extStatus = $this->getExtensionInfo( $type, $directory, $name );
+                               if ( $extStatus->isOK() ) {
+                                       $extensions[] = $name;
+                               } else {
+                                       $status->merge( $extStatus );
+                               }
+                       }
+               }
+               $extensions = array_unique( $extensions );
+               $status->value = $extensions;
+               return $status;
+       }
+
        /**
         * Main entry point.
         */
index 8634f89..0194822 100644 (file)
@@ -1300,7 +1300,7 @@ abstract class DatabaseUpdater {
         */
        protected function migrateActors() {
                global $wgActorTableSchemaMigrationStage;
-               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW &&
+               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
                        !$this->updateRowExists( 'MigrateActors' )
                ) {
                        $this->output(
index 0f8a5b0..d51ea2e 100644 (file)
@@ -1264,15 +1264,33 @@ abstract class Installer {
        }
 
        /**
-        * Finds extensions that follow the format /$directory/Name/Name.php,
-        * and returns an array containing the value for 'Name' for each found extension.
+        * Find extensions or skins in a subdirectory of $IP.
+        * Returns an array containing the value for 'Name' for each found extension.
         *
-        * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
-        *
-        * @param string $directory Directory to search in
+        * @param string $directory Directory to search in, relative to $IP, must be either "extensions"
+        *     or "skins"
         * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
         */
        public function findExtensions( $directory = 'extensions' ) {
+               switch ( $directory ) {
+                       case 'extensions':
+                               return $this->findExtensionsByType( 'extension', 'extensions' );
+                       case 'skins':
+                               return $this->findExtensionsByType( 'skin', 'skins' );
+                       default:
+                               throw new InvalidArgumentException( "Invalid extension type" );
+               }
+       }
+
+       /**
+        * Find extensions or skins, and return an array containing the value for 'Name' for each found
+        * extension.
+        *
+        * @param string $type Either "extension" or "skin"
+        * @param string $directory Directory to search in, relative to $IP
+        * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
+        */
+       protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
                if ( $this->getVar( 'IP' ) === null ) {
                        return [];
                }
@@ -1282,40 +1300,15 @@ abstract class Installer {
                        return [];
                }
 
-               // extensions -> extension.json, skins -> skin.json
-               $jsonFile = substr( $directory, 0, strlen( $directory ) - 1 ) . '.json';
-
                $dh = opendir( $extDir );
                $exts = [];
                while ( ( $file = readdir( $dh ) ) !== false ) {
                        if ( !is_dir( "$extDir/$file" ) ) {
                                continue;
                        }
-                       $fullJsonFile = "$extDir/$file/$jsonFile";
-                       $isJson = file_exists( $fullJsonFile );
-                       $isPhp = false;
-                       if ( !$isJson ) {
-                               // Only fallback to PHP file if JSON doesn't exist
-                               $fullPhpFile = "$extDir/$file/$file.php";
-                               $isPhp = file_exists( $fullPhpFile );
-                       }
-                       if ( $isJson || $isPhp ) {
-                               // Extension exists. Now see if there are screenshots
-                               $exts[$file] = [];
-                               if ( is_dir( "$extDir/$file/screenshots" ) ) {
-                                       $paths = glob( "$extDir/$file/screenshots/*.png" );
-                                       foreach ( $paths as $path ) {
-                                               $exts[$file]['screenshots'][] = str_replace( $extDir, "../$directory", $path );
-                                       }
-
-                               }
-                       }
-                       if ( $isJson ) {
-                               $info = $this->readExtension( $fullJsonFile );
-                               if ( $info === false ) {
-                                       continue;
-                               }
-                               $exts[$file] += $info;
+                       $status = $this->getExtensionInfo( $type, $directory, $file );
+                       if ( $status->isOK() ) {
+                               $exts[$file] = $status->value;
                        }
                }
                closedir( $dh );
@@ -1324,12 +1317,65 @@ abstract class Installer {
                return $exts;
        }
 
+       /**
+        * @param string $type Either "extension" or "skin"
+        * @param string $parentRelPath The parent directory relative to $IP
+        * @param string $name The extension or skin name
+        * @return Status An object containing an error list. If there were no errors, an associative
+        *     array of information about the extension can be found in $status->value.
+        */
+       protected function getExtensionInfo( $type, $parentRelPath, $name ) {
+               if ( $this->getVar( 'IP' ) === null ) {
+                       throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
+               }
+               if ( $type !== 'extension' && $type !== 'skin' ) {
+                       throw new InvalidArgumentException( "Invalid extension type" );
+               }
+               $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
+               $relDir = "../$parentRelPath/$name";
+               if ( !is_dir( $absDir ) ) {
+                       return Status::newFatal( 'config-extension-not-found', $name );
+               }
+               $jsonFile = $type . '.json';
+               $fullJsonFile = "$absDir/$jsonFile";
+               $isJson = file_exists( $fullJsonFile );
+               $isPhp = false;
+               if ( !$isJson ) {
+                       // Only fallback to PHP file if JSON doesn't exist
+                       $fullPhpFile = "$absDir/$name.php";
+                       $isPhp = file_exists( $fullPhpFile );
+               }
+               if ( !$isJson && !$isPhp ) {
+                       return Status::newFatal( 'config-extension-not-found', $name );
+               }
+
+               // Extension exists. Now see if there are screenshots
+               $info = [];
+               if ( is_dir( "$absDir/screenshots" ) ) {
+                       $paths = glob( "$absDir/screenshots/*.png" );
+                       foreach ( $paths as $path ) {
+                               $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
+                       }
+               }
+
+               if ( $isJson ) {
+                       $jsonStatus = $this->readExtension( $fullJsonFile );
+                       if ( !$jsonStatus->isOK() ) {
+                               return $jsonStatus;
+                       }
+                       $info += $jsonStatus->value;
+               }
+
+               return Status::newGood( $info );
+       }
+
        /**
         * @param string $fullJsonFile
         * @param array $extDeps
         * @param array $skinDeps
         *
-        * @return array|bool False if this extension can't be loaded
+        * @return Status On success, an array of extension information is in $status->value. On
+        *    failure, the Status object will have an error list.
         */
        private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
                $load = [
@@ -1340,7 +1386,7 @@ abstract class Installer {
                        foreach ( $extDeps as $dep ) {
                                $fname = "$extDir/$dep/extension.json";
                                if ( !file_exists( $fname ) ) {
-                                       return false;
+                                       return Status::newFatal( 'config-extension-not-found', $dep );
                                }
                                $load[$fname] = 1;
                        }
@@ -1350,7 +1396,7 @@ abstract class Installer {
                        foreach ( $skinDeps as $dep ) {
                                $fname = "$skinDir/$dep/skin.json";
                                if ( !file_exists( $fname ) ) {
-                                       return false;
+                                       return Status::newFatal( 'config-extension-not-found', $dep );
                                }
                                $load[$fname] = 1;
                        }
@@ -1364,7 +1410,8 @@ abstract class Installer {
                        ) {
                                // If something is incompatible with a dependency, we have no real
                                // option besides skipping it
-                               return false;
+                               return Status::newFatal( 'config-extension-dependency',
+                                       basename( dirname( $fullJsonFile ) ), $e->getMessage() );
                        } elseif ( $e->missingExtensions || $e->missingSkins ) {
                                // There's an extension missing in the dependency tree,
                                // so add those to the dependency list and try again
@@ -1375,7 +1422,8 @@ abstract class Installer {
                                );
                        }
                        // Some other kind of dependency error?
-                       return false;
+                       return Status::newFatal( 'config-extension-dependency',
+                               basename( dirname( $fullJsonFile ) ), $e->getMessage() );
                }
                $ret = [];
                // The order of credits will be the order of $load,
@@ -1397,7 +1445,7 @@ abstract class Installer {
                }
                $ret['type'] = $credits['type'];
 
-               return $ret;
+               return Status::newGood( $ret );
        }
 
        /**
index c89be17..893df5a 100644 (file)
        "config-skins-screenshot": "$1 ($2)",
        "config-extensions-requires": "$1 (requires $2)",
        "config-screenshot": "screenshot",
+       "config-extension-not-found": "Could not find the registration file for the extension \"$1\"",
+       "config-extension-dependency": "A dependency error was encountered while installing the extension \"$1\": $2",
        "mainpagetext": "<strong>MediaWiki has been installed.</strong>",
        "mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index e423bcd..39dbbca 100644 (file)
        "config-skins-screenshot": "Radio button text, $1 is the skin name, and $2 is a link to a screenshot of that skin, where the link text is {{msg-mw|config-screenshot}}.",
        "config-extensions-requires": "Radio button text, $1 is the extension name, and $2 are links to other extensions that this one requires.\n{{Identical|Require}}",
        "config-screenshot": "Link text for the link in {{msg-mw|config-skins-screenshot}}\n{{Identical|Screenshot}}",
+       "config-extension-not-found": "An error shown when an extension or skin named by the user could not be found.\n* $1 is the extension name",
+       "config-extension-dependency": "An error shown if an extension could not be loaded due to it depending on the wrong version of MediaWiki or an uninstallable extension.\n* $1 is the extension name\n* $2 is a more detailed explanation, in English",
        "mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
        "mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
 }
index f87a336..63575eb 100644 (file)
@@ -134,4 +134,16 @@ class ThumbnailRenderJob extends Job {
                }
                return false;
        }
+
+       /**
+        * Whether to retry the job.
+        * @return bool
+        */
+       public function allowRetries() {
+               // ThumbnailRenderJob is a warmup for the thumbnails cache,
+               // so loosing it is not a problem. Most times the job fails
+               // for non-renderable or missing images which will not be fixed
+               // by a retry, but will create additional load on the renderer.
+               return false;
+       }
 }
index ba251ba..f693dd5 100644 (file)
@@ -375,6 +375,11 @@ class DBConnRef implements IDatabase {
                throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
        }
 
+       public function selectDomain( $domain ) {
+               // Disallow things that might confuse the LoadBalancer tracking
+               throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
+       }
+
        public function getDBname() {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
index a091242..91cb881 100644 (file)
@@ -81,8 +81,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        protected $user;
        /** @var string Password used to establish the current connection */
        protected $password;
-       /** @var string Database that this instance is currently connected to */
-       protected $dbName;
        /** @var array[] Map of (table => (dbname, schema, prefix) map) */
        protected $tableAliases = [];
        /** @var string[] Map of (index alias => index) */
@@ -120,10 +118,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /** @var bool Whether to suppress triggering of transaction end callbacks */
        protected $trxEndCallbacksSuppressed = false;
 
-       /** @var string */
-       protected $tablePrefix = '';
-       /** @var string */
-       protected $schema = '';
        /** @var int */
        protected $flags;
        /** @var array */
@@ -291,13 +285,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @param array $params Parameters passed from Database::factory()
         */
        protected function __construct( array $params ) {
-               foreach ( [ 'host', 'user', 'password', 'dbname' ] as $name ) {
+               foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
                        $this->connectionParams[$name] = $params[$name];
                }
 
-               $this->schema = $params['schema'];
-               $this->tablePrefix = $params['tablePrefix'];
-
                $this->cliMode = $params['cliMode'];
                // Agent name is added to SQL queries in a comment, so make sure it can't break out
                $this->agent = str_replace( '/', '-', $params['agent'] );
@@ -329,7 +320,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                // Set initial dummy domain until open() sets the final DB/prefix
-               $this->currentDomain = DatabaseDomain::newUnspecified();
+               $this->currentDomain = new DatabaseDomain(
+                       $params['dbname'] != '' ? $params['dbname'] : null,
+                       $params['schema'] != '' ? $params['schema'] : null,
+                       $params['tablePrefix']
+               );
        }
 
        /**
@@ -346,11 +341,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
                // Establish the connection
                $this->doInitConnection();
-               // Set the domain object after open() sets the relevant fields
-               if ( $this->dbName != '' ) {
-                       // Domains with server scope but a table prefix are not used by IDatabase classes
-                       $this->currentDomain = new DatabaseDomain( $this->dbName, null, $this->tablePrefix );
-               }
        }
 
        /**
@@ -366,7 +356,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->connectionParams['host'],
                                $this->connectionParams['user'],
                                $this->connectionParams['password'],
-                               $this->connectionParams['dbname']
+                               $this->connectionParams['dbname'],
+                               $this->connectionParams['schema'],
+                               $this->connectionParams['tablePrefix']
                        );
                } else {
                        throw new InvalidArgumentException( "No database user provided." );
@@ -380,10 +372,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @param string $user Database user name
         * @param string $password Database user password
         * @param string $dbName Database name
+        * @param string|null $schema Database schema name
+        * @param string $tablePrefix Table prefix
         * @return bool
         * @throws DBConnectionError
         */
-       abstract protected function open( $server, $user, $password, $dbName );
+       abstract protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix );
 
        /**
         * Construct a Database subclass instance given a database type and parameters
@@ -441,7 +435,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $p['flags'] = $p['flags'] ?? 0;
                        $p['variables'] = $p['variables'] ?? [];
                        $p['tablePrefix'] = $p['tablePrefix'] ?? '';
-                       $p['schema'] = $p['schema'] ?? '';
+                       $p['schema'] = $p['schema'] ?? null;
                        $p['cliMode'] = $p['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
                        $p['agent'] = $p['agent'] ?? '';
                        if ( !isset( $p['connLogger'] ) ) {
@@ -599,24 +593,37 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function tablePrefix( $prefix = null ) {
-               $old = $this->tablePrefix;
+               $old = $this->currentDomain->getTablePrefix();
                if ( $prefix !== null ) {
-                       $this->tablePrefix = $prefix;
-                       $this->currentDomain = ( $this->dbName != '' )
-                               ? new DatabaseDomain( $this->dbName, null, $this->tablePrefix )
-                               : DatabaseDomain::newUnspecified();
+                       $this->currentDomain = new DatabaseDomain(
+                               $this->currentDomain->getDatabase(),
+                               $this->currentDomain->getSchema(),
+                               $prefix
+                       );
                }
 
                return $old;
        }
 
        public function dbSchema( $schema = null ) {
-               $old = $this->schema;
+               $old = $this->currentDomain->getSchema();
                if ( $schema !== null ) {
-                       $this->schema = $schema;
+                       $this->currentDomain = new DatabaseDomain(
+                               $this->currentDomain->getDatabase(),
+                               // DatabaseDomain uses null for unspecified schemas
+                               strlen( $schema ) ? $schema : null,
+                               $this->currentDomain->getTablePrefix()
+                       );
                }
 
-               return $old;
+               return (string)$old;
+       }
+
+       /**
+        * @return string Schema to use to qualify relations in queries
+        */
+       protected function relationSchemaQualifier() {
+               return $this->dbSchema();
        }
 
        public function getLBInfo( $name = null ) {
@@ -900,7 +907,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return array_merge(
                        [
                                'db_server' => $this->server,
-                               'db_name' => $this->dbName,
+                               'db_name' => $this->getDBname(),
                                'db_user' => $this->user,
                        ],
                        $extras
@@ -2287,17 +2294,26 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return false;
        }
 
-       public function selectDB( $db ) {
-               # Stub. Shouldn't cause serious problems if it's not overridden, but
-               # if your database engine supports a concept similar to MySQL's
-               # databases you may as well.
-               $this->dbName = $db;
+       final public function selectDB( $db ) {
+               $this->selectDomain( new DatabaseDomain(
+                       $db,
+                       $this->currentDomain->getSchema(),
+                       $this->currentDomain->getTablePrefix()
+               ) );
 
                return true;
        }
 
+       final public function selectDomain( $domain ) {
+               $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
+       }
+
+       protected function doSelectDomain( DatabaseDomain $domain ) {
+               $this->currentDomain = $domain;
+       }
+
        public function getDBname() {
-               return $this->dbName;
+               return $this->currentDomain->getDatabase();
        }
 
        public function getServer() {
@@ -2382,14 +2398,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $database = $this->tableAliases[$table]['dbname'];
                                $schema = is_string( $this->tableAliases[$table]['schema'] )
                                        ? $this->tableAliases[$table]['schema']
-                                       : $this->schema;
+                                       : $this->relationSchemaQualifier();
                                $prefix = is_string( $this->tableAliases[$table]['prefix'] )
                                        ? $this->tableAliases[$table]['prefix']
-                                       : $this->tablePrefix;
+                                       : $this->tablePrefix();
                        } else {
                                $database = '';
-                               $schema = $this->schema; # Default schema
-                               $prefix = $this->tablePrefix; # Default prefix
+                               $schema = $this->relationSchemaQualifier(); # Default schema
+                               $prefix = $this->tablePrefix(); # Default prefix
                        }
                }
 
@@ -4109,7 +4125,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->opened = false;
                $this->conn = false;
                try {
-                       $this->open( $this->server, $this->user, $this->password, $this->dbName );
+                       $this->open(
+                               $this->server,
+                               $this->user,
+                               $this->password,
+                               $this->getDBname(),
+                               $this->dbSchema(),
+                               $this->tablePrefix()
+                       );
                        $this->lastPing = microtime( true );
                        $ok = true;
 
@@ -4643,7 +4666,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->conn = false;
                        $this->trxEndCallbacks = []; // don't copy
                        $this->handleSessionLoss(); // no trx or locks anymore
-                       $this->open( $this->server, $this->user, $this->password, $this->dbName );
+                       $this->open(
+                               $this->server,
+                               $this->user,
+                               $this->password,
+                               $this->getDBname(),
+                               $this->dbSchema(),
+                               $this->tablePrefix()
+                       );
                        $this->lastPing = microtime( true );
                }
        }
index 1246e44..61367f5 100644 (file)
@@ -77,7 +77,7 @@ class DatabaseMssql extends Database {
                parent::__construct( $params );
        }
 
-       protected function open( $server, $user, $password, $dbName ) {
+       protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
                # Test for driver support, to avoid suppressed fatal error
                if ( !function_exists( 'sqlsrv_connect' ) ) {
                        throw new DBConnectionError(
@@ -96,11 +96,10 @@ class DatabaseMssql extends Database {
                $this->server = $server;
                $this->user = $user;
                $this->password = $password;
-               $this->dbName = $dbName;
 
                $connectionInfo = [];
 
-               if ( $dbName ) {
+               if ( $dbName != '' ) {
                        $connectionInfo['Database'] = $dbName;
                }
 
@@ -120,6 +119,11 @@ class DatabaseMssql extends Database {
                }
 
                $this->opened = true;
+               $this->currentDomain = new DatabaseDomain(
+                       ( $dbName != '' ) ? $dbName : null,
+                       null,
+                       $tablePrefix
+               );
 
                return (bool)$this->conn;
        }
@@ -1006,7 +1010,7 @@ class DatabaseMssql extends Database {
                }
 
                if ( $schema === false ) {
-                       $schema = $this->schema;
+                       $schema = $this->dbSchema();
                }
 
                $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
@@ -1167,18 +1171,13 @@ class DatabaseMssql extends Database {
                        $s );
        }
 
-       /**
-        * @param string $db
-        * @return bool
-        */
-       public function selectDB( $db ) {
-               try {
-                       $this->dbName = $db;
-                       $this->query( "USE $db" );
-                       return true;
-               } catch ( Exception $e ) {
-                       return false;
-               }
+       protected function doSelectDomain( DatabaseDomain $domain ) {
+               $encDatabase = $this->addIdentifierQuotes( $domain->getDatabase() );
+               $this->query( "USE $encDatabase" );
+               // Update that domain fields on success (no exception thrown)
+               $this->currentDomain = $domain;
+
+               return true;
        }
 
        /**
@@ -1306,8 +1305,8 @@ class DatabaseMssql extends Database {
        private function populateColumnCaches() {
                $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
                        [
-                               'TABLE_CATALOG' => $this->dbName,
-                               'TABLE_SCHEMA' => $this->schema,
+                               'TABLE_CATALOG' => $this->getDBname(),
+                               'TABLE_SCHEMA' => $this->dbSchema(),
                                'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
                        ] );
 
index 4bd607c..cc9e98f 100644 (file)
@@ -120,18 +120,17 @@ abstract class DatabaseMysqlBase extends Database {
                return 'mysql';
        }
 
-       protected function open( $server, $user, $password, $dbName ) {
+       protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
                # Close/unset connection handle
                $this->close();
 
                $this->server = $server;
                $this->user = $user;
                $this->password = $password;
-               $this->dbName = $dbName;
 
                $this->installErrorHandler();
                try {
-                       $this->conn = $this->mysqlConnect( $this->server );
+                       $this->conn = $this->mysqlConnect( $this->server, $dbName );
                } catch ( Exception $ex ) {
                        $this->restoreErrorHandler();
                        throw $ex;
@@ -156,20 +155,9 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                if ( strlen( $dbName ) ) {
-                       Wikimedia\suppressWarnings();
-                       $success = $this->selectDB( $dbName );
-                       Wikimedia\restoreWarnings();
-                       if ( !$success ) {
-                               $error = $this->lastError();
-                               $this->queryLogger->error(
-                                       "Error selecting database {db_name} on server {db_server}: {error}",
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                               'error' => $error,
-                                       ] )
-                               );
-                               throw new DBConnectionError( $this, "Error selecting database $dbName: $error" );
-                       }
+                       $this->selectDomain( new DatabaseDomain( $dbName, null, $tablePrefix ) );
+               } else {
+                       $this->currentDomain = new DatabaseDomain( null, null, $tablePrefix );
                }
 
                // Tell the server what we're communicating with
@@ -240,10 +228,11 @@ abstract class DatabaseMysqlBase extends Database {
         * Open a connection to a MySQL server
         *
         * @param string $realServer
+        * @param string|null $dbName
         * @return mixed Raw connection
         * @throws DBConnectionError
         */
-       abstract protected function mysqlConnect( $realServer );
+       abstract protected function mysqlConnect( $realServer, $dbName );
 
        /**
         * Set the character set of the MySQL link
@@ -1513,7 +1502,7 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function listViews( $prefix = null, $fname = __METHOD__ ) {
                // The name of the column containing the name of the VIEW
-               $propertyName = 'Tables_in_' . $this->dbName;
+               $propertyName = 'Tables_in_' . $this->getDBname();
 
                // Query for the VIEWS
                $res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
index 6d9dabd..ad9b0a4 100644 (file)
@@ -53,10 +53,11 @@ class DatabaseMysqli extends DatabaseMysqlBase {
 
        /**
         * @param string $realServer
+        * @param string|null $dbName
         * @return bool|mysqli
         * @throws DBConnectionError
         */
-       protected function mysqlConnect( $realServer ) {
+       protected function mysqlConnect( $realServer, $dbName ) {
                # Avoid suppressed fatal error, which is very hard to track down
                if ( !function_exists( 'mysqli_init' ) ) {
                        throw new DBConnectionError( $this, "MySQLi functions missing,"
@@ -111,9 +112,15 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                }
                $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
 
-               if ( $mysqli->real_connect( $realServer, $this->user,
-                       $this->password, $this->dbName, $port, $socket, $connFlags )
-               ) {
+               if ( $mysqli->real_connect(
+                       $realServer,
+                       $this->user,
+                       $this->password,
+                       $dbName,
+                       $port,
+                       $socket,
+                       $connFlags
+               ) ) {
                        return $mysqli;
                }
 
@@ -177,16 +184,22 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                return $conn->affected_rows;
        }
 
-       /**
-        * @param string $db
-        * @return bool
-        */
-       function selectDB( $db ) {
+       function doSelectDomain( DatabaseDomain $domain ) {
                $conn = $this->getBindingHandle();
 
-               $this->dbName = $db;
+               if ( $domain->getSchema() !== null ) {
+                       throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+               }
+
+               $database = $domain->getDatabase();
+               if ( !$conn->select_db( $database ) ) {
+                       throw new DBExpectedError( $this, "Could not select database '$database'." );
+               }
+
+               // Update that domain fields on success (no exception thrown)
+               $this->currentDomain = $domain;
 
-               return $conn->select_db( $db );
+               return true;
        }
 
        /**
index 691a4b7..e1cd764 100644 (file)
@@ -86,7 +86,7 @@ class DatabasePostgres extends Database {
                return false;
        }
 
-       protected function open( $server, $user, $password, $dbName ) {
+       protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
                # Test for Postgres support, to avoid suppressed fatal error
                if ( !function_exists( 'pg_connect' ) ) {
                        throw new DBConnectionError(
@@ -100,7 +100,6 @@ class DatabasePostgres extends Database {
                $this->server = $server;
                $this->user = $user;
                $this->password = $password;
-               $this->dbName = $dbName;
 
                $connectVars = [
                        // pg_connect() user $user as the default database. Since a database is *required*,
@@ -157,30 +156,42 @@ class DatabasePostgres extends Database {
                $this->query( "SET standard_conforming_strings = on", __METHOD__ );
                $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
 
-               $this->determineCoreSchema( $this->schema );
-               // The schema to be used is now in the search path; no need for explicit qualification
-               $this->schema = '';
+               $this->determineCoreSchema( $schema );
+               $this->currentDomain = new DatabaseDomain( $dbName, $schema, $tablePrefix );
 
-               return $this->conn;
+               return (bool)$this->conn;
+       }
+
+       protected function relationSchemaQualifier() {
+               if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
+                       // The schema to be used is now in the search path; no need for explicit qualification
+                       return '';
+               }
+
+               return parent::relationSchemaQualifier();
        }
 
        public function databasesAreIndependent() {
                return true;
        }
 
-       /**
-        * Postgres doesn't support selectDB in the same way MySQL does. So if the
-        * DB name doesn't match the open connection, open a new one
-        * @param string $db
-        * @return bool
-        * @throws DBUnexpectedError
-        */
-       public function selectDB( $db ) {
-               if ( $this->dbName !== $db ) {
-                       return (bool)$this->open( $this->server, $this->user, $this->password, $db );
+       public function doSelectDomain( DatabaseDomain $domain ) {
+               if ( $this->getDBname() !== $domain->getDatabase() ) {
+                       // Postgres doesn't support selectDB in the same way MySQL does.
+                       // So if the DB name doesn't match the open connection, open a new one
+                       $this->open(
+                               $this->server,
+                               $this->user,
+                               $this->password,
+                               $domain->getDatabase(),
+                               $domain->getSchema(),
+                               $domain->getTablePrefix()
+                       );
                } else {
-                       return true;
+                       $this->currentDomain = $domain;
                }
+
+               return true;
        }
 
        /**
@@ -1320,10 +1331,6 @@ SQL;
                return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
        }
 
-       public function getDBname() {
-               return $this->dbName;
-       }
-
        public function getServer() {
                return $this->server;
        }
index 0e6240f..487e122 100644 (file)
@@ -71,6 +71,10 @@ class DatabaseSqlite extends Database {
                if ( isset( $p['dbFilePath'] ) ) {
                        $this->dbPath = $p['dbFilePath'];
                        $lockDomain = md5( $this->dbPath );
+                       // Use "X" for things like X.sqlite and ":memory:" for RAM-only DBs
+                       if ( !isset( $p['dbname'] ) || !strlen( $p['dbname'] ) ) {
+                               $p['dbname'] = preg_replace( '/\.sqlite\d?$/', '', basename( $this->dbPath ) );
+                       }
                } elseif ( isset( $p['dbDirectory'] ) ) {
                        $this->dbDir = $p['dbDirectory'];
                        $lockDomain = $p['dbname'];
@@ -109,7 +113,7 @@ class DatabaseSqlite extends Database {
         */
        public static function newStandaloneInstance( $filename, array $p = [] ) {
                $p['dbFilePath'] = $filename;
-               $p['schema'] = false;
+               $p['schema'] = null;
                $p['tablePrefix'] = '';
                /** @var DatabaseSqlite $db */
                $db = Database::factory( 'sqlite', $p );
@@ -120,7 +124,11 @@ class DatabaseSqlite extends Database {
        protected function doInitConnection() {
                if ( $this->dbPath !== null ) {
                        // Standalone .sqlite file mode.
-                       $this->openFile( $this->dbPath, $this->connectionParams['dbname'] );
+                       $this->openFile(
+                               $this->dbPath,
+                               $this->connectionParams['dbname'],
+                               $this->connectionParams['tablePrefix']
+                       );
                } elseif ( $this->dbDir !== null ) {
                        // Stock wiki mode using standard file names per DB
                        if ( strlen( $this->connectionParams['dbname'] ) ) {
@@ -128,7 +136,9 @@ class DatabaseSqlite extends Database {
                                        $this->connectionParams['host'],
                                        $this->connectionParams['user'],
                                        $this->connectionParams['password'],
-                                       $this->connectionParams['dbname']
+                                       $this->connectionParams['dbname'],
+                                       $this->connectionParams['schema'],
+                                       $this->connectionParams['tablePrefix']
                                );
                        } else {
                                // Caller will manually call open() later?
@@ -155,7 +165,7 @@ class DatabaseSqlite extends Database {
                return false;
        }
 
-       protected function open( $server, $user, $pass, $dbName ) {
+       protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
                $this->close();
                $fileName = self::generateFileName( $this->dbDir, $dbName );
                if ( !is_readable( $fileName ) ) {
@@ -163,7 +173,7 @@ class DatabaseSqlite extends Database {
                        throw new DBConnectionError( $this, "SQLite database not accessible" );
                }
                // Only $dbName is used, the other parameters are irrelevant for SQLite databases
-               $this->openFile( $fileName, $dbName );
+               $this->openFile( $fileName, $dbName, $tablePrefix );
 
                return (bool)$this->conn;
        }
@@ -173,10 +183,11 @@ class DatabaseSqlite extends Database {
         *
         * @param string $fileName
         * @param string $dbName
+        * @param string $tablePrefix
         * @throws DBConnectionError
         * @return PDO|bool SQL connection or false if failed
         */
-       protected function openFile( $fileName, $dbName ) {
+       protected function openFile( $fileName, $dbName, $tablePrefix ) {
                $err = false;
 
                $this->dbPath = $fileName;
@@ -198,7 +209,7 @@ class DatabaseSqlite extends Database {
 
                $this->opened = is_object( $this->conn );
                if ( $this->opened ) {
-                       $this->dbName = $dbName;
+                       $this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
                        # Set error codes only, don't raise exceptions
                        $this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
                        # Enforce LIKE to be case sensitive, just like MySQL
index 0608253..1973322 100644 (file)
@@ -174,14 +174,15 @@ interface IDatabase {
        /**
         * Get/set the table prefix.
         * @param string|null $prefix The table prefix to set, or omitted to leave it unchanged.
-        * @return string The previous table prefix.
+        * @return string The previous table prefix
+        * @throws DBUnexpectedError
         */
        public function tablePrefix( $prefix = null );
 
        /**
         * Get/set the db schema.
         * @param string|null $schema The database schema to set, or omitted to leave it unchanged.
-        * @return string The previous db schema.
+        * @return string The previous db schema
         */
        public function dbSchema( $schema = null );
 
@@ -358,6 +359,10 @@ interface IDatabase {
        public function getFlag( $flag );
 
        /**
+        * Return the currently selected domain ID
+        *
+        * Null components (database/schema) might change once a connection is established
+        *
         * @return string
         */
        public function getDomainID();
@@ -1115,14 +1120,27 @@ interface IDatabase {
         * Change the current database
         *
         * @param string $db
-        * @return bool Success or failure
+        * @return bool True unless an exception was thrown
         * @throws DBConnectionError If databasesAreIndependent() is true and an error occurs
+        * @throws DBError
+        * @deprecated Since 1.32
         */
        public function selectDB( $db );
 
+       /**
+        * Set the current domain (database, schema, and table prefix)
+        *
+        * This will throw an error for some database types if the database unspecified
+        *
+        * @param string|DatabaseDomain $domain
+        * @since 1.32
+        * @throws DBConnectionError
+        */
+       public function selectDomain( $domain );
+
        /**
         * Get the current DB name
-        * @return string
+        * @return string|null
         */
        public function getDBname();
 
index b4c7c8f..d84ba65 100644 (file)
@@ -974,12 +974,11 @@ class LoadBalancer implements ILoadBalancer {
         * @param int $i Server index
         * @param string $domain Domain ID to open
         * @param int $flags Class CONN_* constant bitfield
-        * @return Database
+        * @return Database|bool Returns false on connection error
+        * @throws DBError When database selection fails
         */
        private function openForeignConnection( $i, $domain, $flags = 0 ) {
                $domainInstance = DatabaseDomain::newFromId( $domain );
-               $dbName = $domainInstance->getDatabase();
-               $prefix = $domainInstance->getTablePrefix();
                $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
 
                if ( $autoCommit ) {
@@ -990,6 +989,7 @@ class LoadBalancer implements ILoadBalancer {
                        $connInUseKey = self::KEY_FOREIGN_INUSE;
                }
 
+               /** @var Database $conn */
                if ( isset( $this->conns[$connInUseKey][$i][$domain] ) ) {
                        // Reuse an in-use connection for the same domain
                        $conn = $this->conns[$connInUseKey][$i][$domain];
@@ -1004,19 +1004,18 @@ class LoadBalancer implements ILoadBalancer {
                        // Reuse a free connection from another domain
                        $conn = reset( $this->conns[$connFreeKey][$i] );
                        $oldDomain = key( $this->conns[$connFreeKey][$i] );
-                       if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
-                               $this->lastError = "Error selecting database '$dbName' on server " .
-                                       $conn->getServer() . " from client host {$this->hostname}";
-                               $this->errorConnection = $conn;
-                               $conn = false;
+                       if ( $domainInstance->getDatabase() !== null ) {
+                               $conn->selectDomain( $domainInstance );
                        } else {
-                               $conn->tablePrefix( $prefix );
-                               unset( $this->conns[$connFreeKey][$i][$oldDomain] );
-                               // Note that if $domain is an empty string, getDomainID() might not match it
-                               $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
-                               $this->connLogger->debug( __METHOD__ .
-                                       ": reusing free connection from $oldDomain for $domain" );
+                               // Stay on the current database, but update the schema/prefix
+                               $conn->dbSchema( $domainInstance->getSchema() );
+                               $conn->tablePrefix( $domainInstance->getTablePrefix() );
                        }
+                       unset( $this->conns[$connFreeKey][$i][$oldDomain] );
+                       // Note that if $domain is an empty string, getDomainID() might not match it
+                       $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
+                       $this->connLogger->debug( __METHOD__ .
+                               ": reusing free connection from $oldDomain for $domain" );
                } else {
                        if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
                                throw new InvalidArgumentException( "No server with index '$i'." );
index 5c0af11..2161b66 100644 (file)
@@ -77,7 +77,7 @@ class LoadBalancerSingle extends LoadBalancer {
                ) );
        }
 
-       protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+       protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
                return $this->db;
        }
 }
index b53a486..5706f2d 100644 (file)
@@ -645,7 +645,7 @@ class ManualLogEntry extends LogEntryBase {
                $relations = $this->relations;
 
                // Ensure actor relations are set
-               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH &&
+               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
                        empty( $relations['target_author_actor'] )
                ) {
                        $actorIds = [];
@@ -664,7 +664,7 @@ class ManualLogEntry extends LogEntryBase {
                                $params['authorActors'] = $actorIds;
                        }
                }
-               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW ) {
+               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
                        unset( $relations['target_author_id'], $relations['target_author_ip'] );
                        unset( $params['authorIds'], $params['authorIPs'] );
                }
index 5793966..081af19 100644 (file)
@@ -987,8 +987,16 @@ class WikiPage implements Page, IDBAccessObject {
 
                // rd_fragment and rd_interwiki were added later, populate them if empty
                if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
+                       // (T203942) We can't redirect to Media namespace because it's virtual.
+                       // We don't want to modify Title objects farther down the
+                       // line. So, let's fix this here by changing to File namespace.
+                       if ( $row->rd_namespace == NS_MEDIA ) {
+                               $namespace = NS_FILE;
+                       } else {
+                               $namespace = $row->rd_namespace;
+                       }
                        $this->mRedirectTarget = Title::makeTitle(
-                               $row->rd_namespace, $row->rd_title,
+                               $namespace, $row->rd_title,
                                $row->rd_fragment, $row->rd_interwiki
                        );
                        return $this->mRedirectTarget;
@@ -2914,7 +2922,7 @@ class WikiPage implements Page, IDBAccessObject {
                        if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
                                $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
                        }
-                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
                        }
 
index 445981b..48ba111 100644 (file)
@@ -1290,42 +1290,6 @@ class ParserOutput extends CacheTime {
                );
        }
 
-       // TODO remove this method once old parser cache objects have expired, probably mid-October 2018
-       public function __wakeup() {
-               // T203716 remove wrapper that was added by logic in an older version of this class,
-               // where the wrapper was included in mText. This might sometimes remove a wrapper that's
-               // genuine content (manually added to a system message), but that will work out OK, see below.
-               $text = $this->getRawText();
-               $start = Html::openElement( 'div', [
-                       'class' => 'mw-parser-output'
-               ] );
-               $startLen = strlen( $start );
-               $end = Html::closeElement( 'div' );
-               $endPos = strrpos( $text, $end );
-               $endLen = strlen( $end );
-               if ( substr( $text, 0, $startLen ) === $start && $endPos !== false
-                        // if the closing div is followed by real content, bail out of unwrapping
-                        && preg_match( '/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
-               ) {
-                       $text = substr( $text, $startLen );
-                       $text = substr( $text, 0, $endPos - $startLen ) .
-                                       substr( $text, $endPos - $startLen + $endLen );
-                       $this->setText( $text );
-                       // We found a wrapper to remove, so the ParserOutput was probably created by the
-                       // code path that now contains an addWrapperDivClass( 'mw-parser-output' ) call,
-                       // but it did not contain it when this object was cached, so we need to fix the
-                       // wrapper class variable.
-                       // If this was a message with a manually added wrapper, we are technically wrong about
-                       // this but we were wrong about the unwrapping as well so it will work out just right,
-                       // except when this is a normal page view of such a message page, in which case
-                       // it will be single-wrapped instead of double-wrapped (harmless) or something wants
-                       // render the message with unwrap=true (in which case the message won't be wrapped even
-                       // though it should, but the few code paths using unwrap=true only do it for real pages).
-                       $this->clearWrapperDivClass();
-                       $this->addWrapperDivClass( 'mw-parser-output' );
-               }
-       }
-
        /**
         * Merges internal metadata such as flags, accessed options, and profiling info
         * from $source into this ParserOutput. This should be used whenever the state of $source
index c5cffea..7ddf587 100644 (file)
@@ -218,14 +218,14 @@ abstract class RevDelList extends RevisionListBase {
                                $virtualOldBits |= $removedBits;
 
                                $status->successCount++;
-                               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                        if ( $item->getAuthorId() > 0 ) {
                                                $authorIds[] = $item->getAuthorId();
                                        } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
                                                $authorIPs[] = $item->getAuthorName();
                                        }
                                }
-                               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                        $authorActors[] = $item->getAuthorActor();
                                }
 
@@ -271,11 +271,11 @@ abstract class RevDelList extends RevisionListBase {
 
                // Log it
                $authorFields = [];
-               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $authorFields['authorIds'] = $authorIds;
                        $authorFields['authorIPs'] = $authorIPs;
                }
-               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $authorFields['authorActors'] = $authorActors;
                }
                $this->updateLog(
index 6291e8d..a8bf814 100644 (file)
@@ -67,7 +67,7 @@ class RevisionDeleteUser {
                $userTitle = Title::makeTitleSafe( NS_USER, $name );
                $userDbKey = $userTitle->getDBkey();
 
-               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        # Hide name from live edits
                        $dbw->update(
                                'revision',
@@ -116,7 +116,7 @@ class RevisionDeleteUser {
                        );
                }
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
                        if ( $actorId ) {
                                # Hide name from live edits
index 54afde1..9931614 100644 (file)
@@ -104,30 +104,12 @@ class SpecialLog extends SpecialPage {
                        $offenderName = $opts->getValue( 'offender' );
                        $offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
                        if ( $offender ) {
-                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                        $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
+                               } elseif ( $offender->getId() > 0 ) {
+                                       $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
                                } else {
-                                       if ( $offender->getId() > 0 ) {
-                                               $field = 'target_author_id';
-                                               $value = $offender->getId();
-                                       } else {
-                                               $field = 'target_author_ip';
-                                               $value = $offender->getName();
-                                       }
-                                       if ( !$offender->getActorId() ) {
-                                               $qc = [ 'ls_field' => $field, 'ls_value' => $value ];
-                                       } else {
-                                               $db = wfGetDB( DB_REPLICA );
-                                               $qc = [
-                                                       'ls_field' => [ 'target_author_actor', $field ], // So LogPager::getQueryInfo() works right
-                                                       $db->makeList( [
-                                                               $db->makeList(
-                                                                       [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ], LIST_AND
-                                                               ),
-                                                               $db->makeList( [ 'ls_field' => $field, 'ls_value' => $value ], LIST_AND ),
-                                                       ], LIST_OR ),
-                                               ];
-                                       }
+                                       $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
                                }
                        }
                } else {
index 5b50f0a..9900340 100644 (file)
@@ -221,14 +221,12 @@ class ContribsPager extends RangeChronologicalPager {
                                $conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
                                $queryInfo['conds'][] = $conds['conds'];
                                // Force the appropriate index to avoid bad query plans (T189026)
-                               if ( count( $conds['orconds'] ) === 1 ) {
-                                       if ( isset( $conds['orconds']['actor'] ) ) {
-                                               // @todo: This will need changing when revision_comment_temp goes away
-                                               $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
-                                       } else {
-                                               $queryInfo['options']['USE INDEX']['revision'] =
-                                                       isset( $conds['orconds']['userid'] ) ? 'user_timestamp' : 'usertext_timestamp';
-                                       }
+                               if ( isset( $conds['orconds']['actor'] ) ) {
+                                       // @todo: This will need changing when revision_comment_temp goes away
+                                       $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
+                               } else {
+                                       $queryInfo['options']['USE INDEX']['revision'] =
+                                               isset( $conds['orconds']['userid'] ) ? 'user_timestamp' : 'usertext_timestamp';
                                }
                        }
                }
index c214f1f..6b7e4b8 100644 (file)
@@ -113,7 +113,7 @@ class NewFilesPager extends RangeChronologicalPager {
                        $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
                        $conds['rc_namespace'] = NS_FILE;
 
-                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
                                $jcond = 'rc_actor = ' . $imgQuery['fields']['img_actor'];
                        } else {
                                $rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
index 7787901..fe9a5c9 100644 (file)
@@ -627,9 +627,12 @@ class User implements IDBAccessObject, UserIdentity {
        public static function newFromActorId( $id ) {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+               // but it does little harm and might be needed for write callers loading a User.
+               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
                        throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage is MIGRATION_OLD'
+                               'Cannot use ' . __METHOD__
+                                       . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
                        );
                }
 
@@ -679,7 +682,9 @@ class User implements IDBAccessObject, UserIdentity {
                $user = new User;
                $user->mFrom = 'defaults';
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD && $actorId !== null ) {
+               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+               // but it does little harm and might be needed for write callers loading a User.
+               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
                        $user->mActorId = (int)$actorId;
                        if ( $user->mActorId !== 0 ) {
                                $user->mFrom = 'actor';
@@ -1488,7 +1493,9 @@ class User implements IDBAccessObject, UserIdentity {
 
                $this->mGroupMemberships = null; // deferred
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+               // but it does little harm and might be needed for write callers loading a User.
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
                        if ( isset( $row->actor_id ) ) {
                                $this->mActorId = (int)$row->actor_id;
                                if ( $this->mActorId !== 0 ) {
@@ -2491,7 +2498,9 @@ class User implements IDBAccessObject, UserIdentity {
        public function getActorId( IDatabase $dbw = null ) {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+               // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
+               // but it does little harm and might be needed for write callers loading a User.
+               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
                        return 0;
                }
 
@@ -2501,7 +2510,7 @@ class User implements IDBAccessObject, UserIdentity {
 
                // Currently $this->mActorId might be null if $this was loaded from a
                // cache entry that was written when $wgActorTableSchemaMigrationStage
-               // was MIGRATION_OLD. Once that is no longer a possibility (i.e. when
+               // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
                // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
                // has been removed), that condition may be removed.
                if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
@@ -4222,7 +4231,7 @@ class User implements IDBAccessObject, UserIdentity {
                                );
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $dbw->update(
                                        'actor',
                                        [ 'actor_name' => $this->mName ],
@@ -4320,9 +4329,10 @@ class User implements IDBAccessObject, UserIdentity {
                        $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
                        if ( $dbw->affectedRows() ) {
                                $newUser = self::newFromId( $dbw->insertId() );
+                               $newUser->mName = $fields['user_name'];
+                               $newUser->updateActorId( $dbw );
                                // Load the user from master to avoid replica lag
                                $newUser->load( self::READ_LATEST );
-                               $newUser->updateActorId( $dbw );
                        } else {
                                $newUser = null;
                        }
@@ -4431,7 +4441,7 @@ class User implements IDBAccessObject, UserIdentity {
        private function updateActorId( IDatabase $dbw ) {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $dbw->insert(
                                'actor',
                                [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
@@ -5653,14 +5663,18 @@ class User implements IDBAccessObject, UserIdentity {
                        ],
                        'joins' => [],
                ];
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+
+               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+               // but it does little harm and might be needed for write callers loading a User.
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
                        $ret['tables']['user_actor'] = 'actor';
                        $ret['fields'][] = 'user_actor.actor_id';
                        $ret['joins']['user_actor'] = [
-                               $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
                                [ 'user_actor.actor_user = user_id' ]
                        ];
                }
+
                return $ret;
        }
 
index 1b9dac0..938503c 100644 (file)
@@ -40,8 +40,6 @@ in the load balancer, usually indicating a replication environment.' );
        }
 
        public function execute() {
-               global $wgActorTableSchemaMigrationStage;
-
                $dbw = $this->getDB( DB_MASTER );
 
                // Autodetect mode...
@@ -56,15 +54,6 @@ in the load balancer, usually indicating a replication environment.' );
 
                $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
 
-               $needSpecialQuery = ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
-                       $wgActorTableSchemaMigrationStage !== MIGRATION_NEW );
-               if ( $needSpecialQuery ) {
-                       foreach ( $actorQuery['joins'] as &$j ) {
-                               $j[0] = 'JOIN'; // replace LEFT JOIN
-                       }
-                       unset( $j );
-               }
-
                if ( $backgroundMode ) {
                        $this->output( "Using replication-friendly background mode...\n" );
 
@@ -77,54 +66,15 @@ in the load balancer, usually indicating a replication environment.' );
                        for ( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
                                $max = $min + $chunkSize;
 
-                               if ( $needSpecialQuery ) {
-                                       // Use separate subqueries to collect counts with the old
-                                       // and new schemas, to avoid having to do whole-table scans.
-                                       $result = $dbr->select(
-                                               [
-                                                       'user',
-                                                       'rev1' => '('
-                                                               . $dbr->selectSQLText(
-                                                                       [ 'revision', 'revision_actor_temp' ],
-                                                                       [ 'rev_user', 'ct' => 'COUNT(*)' ],
-                                                                       [
-                                                                               "rev_user > $min AND rev_user <= $max",
-                                                                               'revactor_rev' => null,
-                                                                       ],
-                                                                       __METHOD__,
-                                                                       [ 'GROUP BY' => 'rev_user' ],
-                                                                       [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
-                                                               ) . ')',
-                                                       'rev2' => '('
-                                                               . $dbr->selectSQLText(
-                                                                       [ 'revision' ] + $actorQuery['tables'],
-                                                                       [ 'actor_user', 'ct' => 'COUNT(*)' ],
-                                                                       "actor_user > $min AND actor_user <= $max",
-                                                                       __METHOD__,
-                                                                       [ 'GROUP BY' => 'actor_user' ],
-                                                                       $actorQuery['joins']
-                                                               ) . ')',
-                                               ],
-                                               [ 'user_id', 'user_editcount' => 'COALESCE(rev1.ct,0) + COALESCE(rev2.ct,0)' ],
-                                               "user_id > $min AND user_id <= $max",
-                                               __METHOD__,
-                                               [],
-                                               [
-                                                       'rev1' => [ 'LEFT JOIN', 'user_id = rev_user' ],
-                                                       'rev2' => [ 'LEFT JOIN', 'user_id = actor_user' ],
-                                               ]
-                                       );
-                               } else {
-                                       $revUser = $actorQuery['fields']['rev_user'];
-                                       $result = $dbr->select(
-                                               [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
-                                               [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
-                                               "user_id > $min AND user_id <= $max",
-                                               __METHOD__,
-                                               [ 'GROUP BY' => 'user_id' ],
-                                               [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
-                                       );
-                               }
+                               $revUser = $actorQuery['fields']['rev_user'];
+                               $result = $dbr->select(
+                                       [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
+                                       [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
+                                       "user_id > $min AND user_id <= $max",
+                                       __METHOD__,
+                                       [ 'GROUP BY' => 'user_id' ],
+                                       [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
+                               );
 
                                foreach ( $result as $row ) {
                                        $dbw->update( 'user',
@@ -149,41 +99,15 @@ in the load balancer, usually indicating a replication environment.' );
                        $this->output( "Using single-query mode...\n" );
 
                        $user = $dbw->tableName( 'user' );
-                       if ( $needSpecialQuery ) {
-                               $subquery1 = $dbw->selectSQLText(
-                                       [ 'revision', 'revision_actor_temp' ],
-                                       [ 'COUNT(*)' ],
-                                       [
-                                               'user_id = rev_user',
-                                               'revactor_rev' => null,
-                                       ],
-                                       __METHOD__,
-                                       [],
-                                       [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
-                               );
-                               $subquery2 = $dbw->selectSQLText(
-                                       [ 'revision' ] + $actorQuery['tables'],
-                                       [ 'COUNT(*)' ],
-                                       'user_id = actor_user',
-                                       __METHOD__,
-                                       [],
-                                       $actorQuery['joins']
-                               );
-                               $dbw->query(
-                                       "UPDATE $user SET user_editcount=($subquery1) + ($subquery2)",
-                                       __METHOD__
-                               );
-                       } else {
-                               $subquery = $dbw->selectSQLText(
-                                       [ 'revision' ] + $actorQuery['tables'],
-                                       [ 'COUNT(*)' ],
-                                       [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
-                                       __METHOD__,
-                                       [],
-                                       $actorQuery['joins']
-                               );
-                               $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
-                       }
+                       $subquery = $dbw->selectSQLText(
+                               [ 'revision' ] + $actorQuery['tables'],
+                               [ 'COUNT(*)' ],
+                               [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
+                               __METHOD__,
+                               [],
+                               $actorQuery['joins']
+                       );
+                       $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
                }
 
                $this->output( "Done!\n" );
index 438e9dc..3395458 100644 (file)
@@ -90,6 +90,10 @@ class CommandLineInstaller extends Maintenance {
                $this->addOption( 'env-checks', "Run environment checks only, don't change anything" );
 
                $this->addOption( 'with-extensions', "Detect and include extensions" );
+               $this->addOption( 'extensions', 'Comma-separated list of extensions to install',
+                       false, true, false, true );
+               $this->addOption( 'skins', 'Comma-separated list of skins to install (default: all)',
+                       false, true, false, true );
        }
 
        public function getDbType() {
index edd5dda..5e27ac8 100644 (file)
@@ -45,9 +45,9 @@ class MigrateActors extends LoggedUpdateMaintenance {
        protected function doDBUpdates() {
                global $wgActorTableSchemaMigrationStage;
 
-               if ( $wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW ) {
+               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
                        $this->output(
-                               "...cannot update while \$wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
+                               "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
                        );
                        return false;
                }
@@ -266,7 +266,6 @@ class MigrateActors extends LoggedUpdateMaintenance {
                                        $table,
                                        [
                                                $actorField => $row->actor_id,
-                                               $nameField => '',
                                        ],
                                        array_intersect_key( (array)$row, $pkFilter ) + [
                                                $actorField => 0
@@ -377,7 +376,6 @@ class MigrateActors extends LoggedUpdateMaintenance {
                                }
                                $this->beginTransaction( $dbw, __METHOD__ );
                                $dbw->insert( $newTable, $inserts, __METHOD__ );
-                               $dbw->update( $table, [ $nameField => '' ], [ $primaryKey => $updates ], __METHOD__ );
                                $countUpdated += $dbw->affectedRows();
                                $this->commitTransaction( $dbw, __METHOD__ );
                        }
index 589be48..e80b6f6 100644 (file)
@@ -117,15 +117,17 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                        $tables = [ self::$tableMap[$prefix] ];
                                        $fields = [];
                                        $joins = [];
-                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                                               // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
                                                $fields['userid'] = $prefix . '_user';
                                                $fields['username'] = $prefix . '_user_text';
                                        }
-                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                                               // Read the new fields if we're writing them regardless of read mode, to handle upgrades
                                                if ( $prefix === 'rev' ) {
                                                        $tables[] = 'revision_actor_temp';
                                                        $joins['revision_actor_temp'] = [
-                                                               $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ? 'LEFT JOIN' : 'JOIN',
                                                                'rev_id = revactor_rev',
                                                        ];
                                                        $fields['actorid'] = 'revactor_actor';
@@ -147,11 +149,13 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                        $log->addRelations( 'log_id', $items, $row->log_id );
                                        // Query item author relations...
                                        $fields = [];
-                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                                               // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
                                                $fields['userid'] = 'log_user';
                                                $fields['username'] = 'log_user_text';
                                        }
-                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                                               // Read the new fields if we're writing them regardless of read mode, to handle upgrades
                                                $fields['actorid'] = 'log_actor';
                                        }
 
@@ -163,14 +167,14 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                // Add item author relations...
                                $userIds = $userIPs = $userActors = [];
                                foreach ( $sres as $srow ) {
-                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                                if ( $srow->userid > 0 ) {
                                                        $userIds[] = intval( $srow->userid );
                                                } elseif ( $srow->username != '' ) {
                                                        $userIPs[] = $srow->username;
                                                }
                                        }
-                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                                if ( $srow->actorid ) {
                                                        $userActors[] = intval( $srow->actorid );
                                                } elseif ( $srow->userid > 0 ) {
@@ -181,11 +185,11 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                        }
                                }
                                // Add item author relations...
-                               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                        $log->addRelations( 'target_author_id', $userIds, $row->log_id );
                                        $log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
                                }
-                               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                        $log->addRelations( 'target_author_actor', $userActors, $row->log_id );
                                }
                        }
index d90a4a7..98025d1 100644 (file)
@@ -136,20 +136,19 @@ class ReassignEdits extends Maintenance {
                        if ( $total ) {
                                # Reassign edits
                                $this->output( "\nReassigning current edits..." );
-                               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                        $dbw->update(
                                                'revision',
                                                [
                                                        'rev_user' => $to->getId(),
-                                                       'rev_user_text' =>
-                                                               $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $to->getName() : ''
+                                                       'rev_user_text' => $to->getName(),
                                                ],
                                                $from->isLoggedIn()
                                                        ? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
                                                __METHOD__
                                        );
                                }
-                               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                        $dbw->update(
                                                'revision_actor_temp',
                                                [ 'revactor_actor' => $to->getActorId( $dbw ) ],
@@ -179,7 +178,7 @@ class ReassignEdits extends Maintenance {
        }
 
        /**
-        * Return user specifications
+        * Return user specifications for an UPDATE
         * i.e. user => id, user_text => text
         *
         * @param IDatabase $dbw Database handle
@@ -193,13 +192,13 @@ class ReassignEdits extends Maintenance {
                global $wgActorTableSchemaMigrationStage;
 
                $ret = [];
-               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $ret += [
                                $idfield => $user->getId(),
-                               $utfield => $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $user->getName() : '',
+                               $utfield => $user->getName(),
                        ];
                }
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $ret += [ $acfield => $user->getActorId( $dbw ) ];
                }
                return $ret;
index 3fa30cb..6b2f488 100644 (file)
@@ -48,7 +48,7 @@ class RemoveUnusedAccounts extends Maintenance {
                $delUser = [];
                $delActor = [];
                $dbr = $this->getDB( DB_REPLICA );
-               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $res = $dbr->select(
                                [ 'user', 'actor' ],
                                [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
@@ -94,7 +94,7 @@ class RemoveUnusedAccounts extends Maintenance {
                        $this->output( "\nDeleting unused accounts..." );
                        $dbw = $this->getDB( DB_MASTER );
                        $dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
-                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                # Keep actor rows referenced from ipblocks
                                $keep = $dbw->selectFieldValues(
                                        'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
@@ -110,11 +110,11 @@ class RemoveUnusedAccounts extends Maintenance {
                        $dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
-                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
                                $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
                        }
-                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
                                $dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
                        }
index 878eb9b..0ea5db5 100644 (file)
@@ -99,18 +99,16 @@ class RollbackEdits extends Maintenance {
                $titles = [];
                $actorQuery = ActorMigration::newMigration()
                        ->getWhere( $dbr, 'rev_user', User::newFromName( $user, false ) );
-               foreach ( $actorQuery['orconds'] as $cond ) {
-                       $results = $dbr->select(
-                               [ 'page', 'revision' ] + $actorQuery['tables'],
-                               [ 'page_namespace', 'page_title' ],
-                               [ $cond ],
-                               __METHOD__,
-                               [],
-                               [ 'revision' => [ 'JOIN', 'page_latest = rev_id' ] ] + $actorQuery['joins']
-                       );
-                       foreach ( $results as $row ) {
-                               $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
-                       }
+               $results = $dbr->select(
+                       [ 'page', 'revision' ] + $actorQuery['tables'],
+                       [ 'page_namespace', 'page_title' ],
+                       $actorQuery['conds'],
+                       __METHOD__,
+                       [],
+                       [ 'revision' => [ 'JOIN', 'page_latest = rev_id' ] ] + $actorQuery['joins']
+               );
+               foreach ( $results as $row ) {
+                       $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
                }
 
                return $titles;
index c780b6a..2a1feb4 100755 (executable)
@@ -85,7 +85,7 @@ class UpdateMediaWiki extends Maintenance {
        }
 
        function execute() {
-               global $wgVersion, $wgLang, $wgAllowSchemaUpdates;
+               global $wgVersion, $wgLang, $wgAllowSchemaUpdates, $wgMessagesDirs;
 
                if ( !$wgAllowSchemaUpdates
                        && !( $this->hasOption( 'force' )
@@ -111,6 +111,9 @@ class UpdateMediaWiki extends Maintenance {
                        }
                }
 
+               // T206765: We need to load the installer i18n files as some of errors come installer/updater code
+               $wgMessagesDirs['MediawikiInstaller'] = dirname( __DIR__ ) . '/includes/installer/i18n';
+
                $lang = Language::factory( 'en' );
                // Set global language to ensure localised errors are in English (T22633)
                RequestContext::getMain()->setLanguage( $lang );
index cc3ef1c..1f3183f 100644 (file)
@@ -1233,7 +1233,7 @@ class ParserTestRunner {
                        $tables[] = 'image_comment_temp';
                }
 
-               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        // The new tables for actors are in use
                        $tables[] = 'actor';
                        $tables[] = 'revision_actor_temp';
index feab0df..287d28c 100644 (file)
@@ -100,6 +100,14 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        private $mwGlobalsToUnset = [];
 
+       /**
+        * Holds original values of ini settings to be restored
+        * in tearDown().
+        * @see setIniSettings()
+        * @var array
+        */
+       private $iniSettings = [];
+
        /**
         * Holds original loggers which have been replaced by setLogger()
         * @var LoggerInterface[]
@@ -573,6 +581,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                foreach ( $this->mwGlobalsToUnset as $value ) {
                        unset( $GLOBALS[$value] );
                }
+               foreach ( $this->iniSettings as $name => $value ) {
+                       ini_set( $name, $value );
+               }
                if (
                        array_key_exists( 'wgExtraNamespaces', $this->mwGlobals ) ||
                        in_array( 'wgExtraNamespaces', $this->mwGlobalsToUnset )
@@ -722,6 +733,18 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                }
        }
 
+       /**
+        * Set an ini setting for the duration of the test
+        * @param string $name Name of the setting
+        * @param string $value Value to set
+        * @since 1.32
+        */
+       protected function setIniSetting( $name, $value ) {
+               $original = ini_get( $name );
+               $this->iniSettings[$name] = $original;
+               ini_set( $name, $value );
+       }
+
        /**
         * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
         * Otherwise old namespace data will lurk and cause bugs.
index c938750..b761d29 100644 (file)
@@ -27,6 +27,53 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                return new ActorMigration( $stage );
        }
 
+       /**
+        * @dataProvider provideConstructor
+        * @param int $stage
+        * @param string|null $exceptionMsg
+        */
+       public function testConstructor( $stage, $exceptionMsg ) {
+               try {
+                       $m = new ActorMigration( $stage );
+                       if ( $exceptionMsg !== null ) {
+                               $this->fail( 'Expected exception not thrown' );
+                       }
+                       $this->assertInstanceOf( ActorMigration::class, $m );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame( $exceptionMsg, $ex->getMessage() );
+               }
+       }
+
+       public static function provideConstructor() {
+               return [
+                       [ 0, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
+
+                       [ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
+                       [
+                               SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW,
+                               'Cannot read the new schema without also writing it'
+                       ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
+
+                       [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
+                       [
+                               SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD,
+                               'Cannot read the old schema without also writing it'
+                       ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
+
+                       [ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
+               ];
+       }
+
        /**
         * @dataProvider provideGetJoin
         * @param int $stage
@@ -42,7 +89,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
        public static function provideGetJoin() {
                return [
                        'Simple table, old' => [
-                               MIGRATION_OLD, 'rc_user', [
+                               SCHEMA_COMPAT_OLD, 'rc_user', [
                                        'tables' => [],
                                        'fields' => [
                                                'rc_user' => 'rc_user',
@@ -52,34 +99,32 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        'joins' => [],
                                ],
                        ],
-                       'Simple table, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rc_user', [
-                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                       'Simple table, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', [
+                                       'tables' => [],
                                        'fields' => [
-                                               'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
-                                               'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
-                                               'rc_actor' => 'rc_actor',
-                                       ],
-                                       'joins' => [
-                                               'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                               'rc_user' => 'rc_user',
+                                               'rc_user_text' => 'rc_user_text',
+                                               'rc_actor' => 'NULL',
                                        ],
+                                       'joins' => [],
                                ],
                        ],
-                       'Simple table, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rc_user', [
+                       'Simple table, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', [
                                        'tables' => [ 'actor_rc_user' => 'actor' ],
                                        'fields' => [
-                                               'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
-                                               'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+                                               'rc_user' => 'actor_rc_user.actor_user',
+                                               'rc_user_text' => 'actor_rc_user.actor_name',
                                                'rc_actor' => 'rc_actor',
                                        ],
                                        'joins' => [
-                                               'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                               'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
                                        ],
                                ],
                        ],
                        'Simple table, new' => [
-                               MIGRATION_NEW, 'rc_user', [
+                               SCHEMA_COMPAT_NEW, 'rc_user', [
                                        'tables' => [ 'actor_rc_user' => 'actor' ],
                                        'fields' => [
                                                'rc_user' => 'actor_rc_user.actor_user',
@@ -93,7 +138,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'ipblocks, old' => [
-                               MIGRATION_OLD, 'ipb_by', [
+                               SCHEMA_COMPAT_OLD, 'ipb_by', [
                                        'tables' => [],
                                        'fields' => [
                                                'ipb_by' => 'ipb_by',
@@ -103,34 +148,32 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'ipb_by', [
-                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                       'ipblocks, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', [
+                                       'tables' => [],
                                        'fields' => [
-                                               'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
-                                               'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
-                                               'ipb_by_actor' => 'ipb_by_actor',
-                                       ],
-                                       'joins' => [
-                                               'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                               'ipb_by' => 'ipb_by',
+                                               'ipb_by_text' => 'ipb_by_text',
+                                               'ipb_by_actor' => 'NULL',
                                        ],
+                                       'joins' => [],
                                ],
                        ],
-                       'ipblocks, write-new' => [
-                               MIGRATION_WRITE_NEW, 'ipb_by', [
+                       'ipblocks, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', [
                                        'tables' => [ 'actor_ipb_by' => 'actor' ],
                                        'fields' => [
-                                               'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
-                                               'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+                                               'ipb_by' => 'actor_ipb_by.actor_user',
+                                               'ipb_by_text' => 'actor_ipb_by.actor_name',
                                                'ipb_by_actor' => 'ipb_by_actor',
                                        ],
                                        'joins' => [
-                                               'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                               'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
                                        ],
                                ],
                        ],
                        'ipblocks, new' => [
-                               MIGRATION_NEW, 'ipb_by', [
+                               SCHEMA_COMPAT_NEW, 'ipb_by', [
                                        'tables' => [ 'actor_ipb_by' => 'actor' ],
                                        'fields' => [
                                                'ipb_by' => 'actor_ipb_by.actor_user',
@@ -144,7 +187,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'Revision, old' => [
-                               MIGRATION_OLD, 'rev_user', [
+                               SCHEMA_COMPAT_OLD, 'rev_user', [
                                        'tables' => [],
                                        'fields' => [
                                                'rev_user' => 'rev_user',
@@ -154,42 +197,36 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rev_user', [
-                                       'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
-                                               'actor_rev_user' => 'actor',
-                                       ],
+                       'Revision, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', [
+                                       'tables' => [],
                                        'fields' => [
-                                               'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
-                                               'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
-                                               'rev_actor' => 'temp_rev_user.revactor_actor',
-                                       ],
-                                       'joins' => [
-                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
-                                               'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                               'rev_user' => 'rev_user',
+                                               'rev_user_text' => 'rev_user_text',
+                                               'rev_actor' => 'NULL',
                                        ],
+                                       'joins' => [],
                                ],
                        ],
-                       'Revision, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rev_user', [
+                       'Revision, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', [
                                        'tables' => [
                                                'temp_rev_user' => 'revision_actor_temp',
                                                'actor_rev_user' => 'actor',
                                        ],
                                        'fields' => [
-                                               'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
-                                               'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+                                               'rev_user' => 'actor_rev_user.actor_user',
+                                               'rev_user_text' => 'actor_rev_user.actor_name',
                                                'rev_actor' => 'temp_rev_user.revactor_actor',
                                        ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
-                                               'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                        ],
                                ],
                        ],
                        'Revision, new' => [
-                               MIGRATION_NEW, 'rev_user', [
+                               SCHEMA_COMPAT_NEW, 'rev_user', [
                                        'tables' => [
                                                'temp_rev_user' => 'revision_actor_temp',
                                                'actor_rev_user' => 'actor',
@@ -248,34 +285,28 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
                return [
                        'Simple table, old' => [
-                               MIGRATION_OLD, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_OLD, 'rc_user', $genericUser, true, [
                                        'tables' => [],
                                        'orconds' => [ 'userid' => "rc_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'Simple table, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rc_user', $genericUser, true, [
+                       'Simple table, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "rc_actor = '11'",
-                                               'userid' => "rc_actor = '0' AND rc_user = '1'"
-                                       ],
+                                       'orconds' => [ 'userid' => "rc_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'Simple table, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rc_user', $genericUser, true, [
+                       'Simple table, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "rc_actor = '11'",
-                                               'userid' => "rc_actor = '0' AND rc_user = '1'"
-                                       ],
+                                       'orconds' => [ 'actor' => "rc_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, new' => [
-                               MIGRATION_NEW, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_NEW, 'rc_user', $genericUser, true, [
                                        'tables' => [],
                                        'orconds' => [ 'actor' => "rc_actor = '11'" ],
                                        'joins' => [],
@@ -283,34 +314,28 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'ipblocks, old' => [
-                               MIGRATION_OLD, 'ipb_by', $genericUser, true, [
+                               SCHEMA_COMPAT_OLD, 'ipb_by', $genericUser, true, [
                                        'tables' => [],
                                        'orconds' => [ 'userid' => "ipb_by = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'ipb_by', $genericUser, true, [
+                       'ipblocks, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "ipb_by_actor = '11'",
-                                               'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
-                                       ],
+                                       'orconds' => [ 'userid' => "ipb_by = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, write-new' => [
-                               MIGRATION_WRITE_NEW, 'ipb_by', $genericUser, true, [
+                       'ipblocks, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "ipb_by_actor = '11'",
-                                               'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
-                                       ],
+                                       'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
                        'ipblocks, new' => [
-                               MIGRATION_NEW, 'ipb_by', $genericUser, true, [
+                               SCHEMA_COMPAT_NEW, 'ipb_by', $genericUser, true, [
                                        'tables' => [],
                                        'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
                                        'joins' => [],
@@ -318,44 +343,32 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'Revision, old' => [
-                               MIGRATION_OLD, 'rev_user', $genericUser, true, [
+                               SCHEMA_COMPAT_OLD, 'rev_user', $genericUser, true, [
                                        'tables' => [],
                                        'orconds' => [ 'userid' => "rev_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rev_user', $genericUser, true, [
-                                       'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
-                                       ],
-                                       'orconds' => [
-                                               'actor' =>
-                                                       "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
-                                               'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
-                                       ],
-                                       'joins' => [
-                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
-                                       ],
+                       'Revision, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'userid' => "rev_user = '1'" ],
+                                       'joins' => [],
                                ],
                        ],
-                       'Revision, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rev_user', $genericUser, true, [
+                       'Revision, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', $genericUser, true, [
                                        'tables' => [
                                                'temp_rev_user' => 'revision_actor_temp',
                                        ],
-                                       'orconds' => [
-                                               'actor' =>
-                                                       "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
-                                               'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
-                                       ],
+                                       'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
                                        ],
                                ],
                        ],
                        'Revision, new' => [
-                               MIGRATION_NEW, 'rev_user', $genericUser, true, [
+                               SCHEMA_COMPAT_NEW, 'rev_user', $genericUser, true, [
                                        'tables' => [
                                                'temp_rev_user' => 'revision_actor_temp',
                                        ],
@@ -367,7 +380,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'Multiple users, old' => [
-                               MIGRATION_OLD, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, true, [
                                        'tables' => [],
                                        'orconds' => [
                                                'userid' => "rc_user IN ('1','2','3') ",
@@ -376,30 +389,25 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        'joins' => [],
                                ],
                        ],
-                       'Multiple users, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, true, [
+                       'Multiple users, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, true, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'actor' => "rc_actor IN ('11','12','34') ",
-                                               'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
-                                               'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                               'userid' => "rc_user IN ('1','2','3') ",
+                                               'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'Multiple users, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, true, [
+                       'Multiple users, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, true, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "rc_actor IN ('11','12','34') ",
-                                               'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
-                                               'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
-                                       ],
+                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, new' => [
-                               MIGRATION_NEW, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, true, [
                                        'tables' => [],
                                        'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
                                        'joins' => [],
@@ -407,7 +415,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        ],
 
                        'Multiple users, no use ID, old' => [
-                               MIGRATION_OLD, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, false, [
                                        'tables' => [],
                                        'orconds' => [
                                                'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
@@ -415,30 +423,24 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        'joins' => [],
                                ],
                        ],
-                       'Multiple users, write-both' => [
-                               MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, false, [
+                       'Multiple users, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, false, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'actor' => "rc_actor IN ('11','12','34') ",
-                                               'username' => "rc_actor = '0' AND "
-                                               . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                               'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'Multiple users, write-new' => [
-                               MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, false, [
+                       'Multiple users, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, false, [
                                        'tables' => [],
-                                       'orconds' => [
-                                               'actor' => "rc_actor IN ('11','12','34') ",
-                                               'username' => "rc_actor = '0' AND "
-                                               . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
-                                       ],
+                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, new' => [
-                               MIGRATION_NEW, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, false, [
                                        'tables' => [],
                                        'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
                                        'joins' => [],
@@ -470,12 +472,34 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        $user->method( 'getActorId' )->willReturn( $this->db->insertId() );
                }
 
+               $stageNames = [
+                       SCHEMA_COMPAT_OLD => 'old',
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => 'write-both-read-old',
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => 'write-both-read-new',
+                       SCHEMA_COMPAT_NEW => 'new',
+               ];
+
                $stages = [
-                       MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
-                       MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
-                               MIGRATION_NEW ],
-                       MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
-                       MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+                       SCHEMA_COMPAT_OLD => [
+                               SCHEMA_COMPAT_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+                               SCHEMA_COMPAT_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+                               SCHEMA_COMPAT_NEW
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+                               SCHEMA_COMPAT_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+                               SCHEMA_COMPAT_NEW
+                       ],
+                       SCHEMA_COMPAT_NEW => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+                               SCHEMA_COMPAT_NEW
+                       ],
                ];
 
                $nameKey = $key . '_text';
@@ -483,7 +507,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
                        if ( $key === 'ipb_by' ) {
-                               $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+                               $extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
                        }
 
                        $w = $this->makeMigration( $writeStage );
@@ -495,17 +519,21 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                $fields = $w->getInsertValues( $this->db, $key, $user );
                        }
 
-                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
-                               $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
-                               $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
+                       if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                               $this->assertSame( $user->getId(), $fields[$key],
+                                       "old field, stage={$stageNames[$writeStage]}" );
+                               $this->assertSame( $user->getName(), $fields[$nameKey],
+                                       "old field, stage={$stageNames[$writeStage]}" );
                        } else {
-                               $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
-                               $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
+                               $this->assertArrayNotHasKey( $key, $fields, "old field, stage={$stageNames[$writeStage]}" );
+                               $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage={$stageNames[$writeStage]}" );
                        }
-                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
-                               $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
+                       if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
+                               $this->assertSame( $user->getActorId(), $fields[$actorKey],
+                                       "new field, stage={$stageNames[$writeStage]}" );
                        } else {
-                               $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
+                               $this->assertArrayNotHasKey( $actorKey, $fields,
+                                       "new field, stage={$stageNames[$writeStage]}" );
                        }
 
                        $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
@@ -527,12 +555,14 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        $queryInfo['joins']
                                );
 
-                               $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
-                               $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
+                               $this->assertSame( $user->getId(), (int)$row->$key,
+                                       "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, id" );
+                               $this->assertSame( $user->getName(), $row->$nameKey,
+                                       "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, name" );
                                $this->assertSame(
-                                       $readStage === MIGRATION_OLD || $writeStage === MIGRATION_OLD ? 0 : $user->getActorId(),
+                                       ( $readStage & SCHEMA_COMPAT_READ_OLD ) ? 0 : $user->getActorId(),
                                        (int)$row->$actorKey,
-                                       "w=$writeStage, r=$readStage, actor"
+                                       "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, actor"
                                );
                        }
                }
@@ -572,10 +602,10 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
        public static function provideStages() {
                return [
-                       'MIGRATION_OLD' => [ MIGRATION_OLD ],
-                       'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
-                       'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
-                       'MIGRATION_NEW' => [ MIGRATION_NEW ],
+                       'old' => [ SCHEMA_COMPAT_OLD ],
+                       'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+                       'read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
+                       'new' => [ SCHEMA_COMPAT_NEW ],
                ];
        }
 
@@ -630,6 +660,12 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
        }
 
        public function testInsertUserIdentity() {
+               $this->setMwGlobals( [
+                       // for User::getActorId()
+                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               ] );
+               $this->overrideMwServices();
+
                $user = $this->getTestUser()->getUser();
                $userIdentity = $this->getMock( UserIdentity::class );
                $userIdentity->method( 'getId' )->willReturn( $user->getId() );
@@ -638,7 +674,7 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
                list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
                        ->insertWithTempTable( $this->db, 'rev_comment', '' );
-               $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+               $m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
                list( $fields, $callback ) =
                        $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
                $extraFields = [
@@ -652,22 +688,22 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                $callback( $id, $extraFields );
                $cCallback( $id );
 
-               $qi = Revision::getQueryInfo();
+               $qi = $m->getJoin( 'rev_user' );
                $row = $this->db->selectRow(
-                       $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+                       [ 'revision' ] + $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
                );
                $this->assertSame( $user->getId(), (int)$row->rev_user );
                $this->assertSame( $user->getName(), $row->rev_user_text );
                $this->assertSame( $user->getActorId(), (int)$row->rev_actor );
 
-               $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+               $m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
                $fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
                $this->assertSame( $user->getId(), $fields['dummy_user'] );
                $this->assertSame( $user->getName(), $fields['dummy_user_text'] );
                $this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
        }
 
-       public function testConstructor() {
+       public function testNewMigration() {
                $m = ActorMigration::newMigration();
                $this->assertInstanceOf( ActorMigration::class, $m );
                $this->assertSame( $m, ActorMigration::newMigration() );
@@ -687,10 +723,12 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
        public static function provideIsAnon() {
                return [
-                       'MIGRATION_OLD' => [ MIGRATION_OLD, 'foo = 0', 'foo != 0' ],
-                       'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH, 'foo = 0', 'foo != 0' ],
-                       'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW, 'foo = 0', 'foo != 0' ],
-                       'MIGRATION_NEW' => [ MIGRATION_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
+                       'old' => [ SCHEMA_COMPAT_OLD, 'foo = 0', 'foo != 0' ],
+                       'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'foo = 0', 'foo != 0' ],
+                       'read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'foo IS NULL', 'foo IS NOT NULL'
+                       ],
+                       'new' => [ SCHEMA_COMPAT_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
                ];
        }
 
index 62094b6..5cc59b3 100644 (file)
@@ -41,10 +41,13 @@ class HtmlTest extends MediaWikiTestCase {
        }
 
        protected function tearDown() {
+               Language::factory( 'en' )->resetNamespaces();
+
                if ( $this->restoreWarnings ) {
                        $this->restoreWarnings = false;
                        Wikimedia\restoreWarnings();
                }
+
                parent::tearDown();
        }
 
index e852bec..7188cf5 100644 (file)
@@ -93,22 +93,14 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                ];
        }
 
-       protected function getCompatActorQueryFields( $prefix, $tmp = false ) {
-               return [
-                       "{$prefix}_user" => "COALESCE( actor_{$prefix}_user.actor_user, {$prefix}_user )",
-                       "{$prefix}_user_text" => "COALESCE( actor_{$prefix}_user.actor_name, {$prefix}_user_text )",
-                       "{$prefix}_actor" => $tmp ?: "{$prefix}_actor",
-               ];
-       }
-
-       protected function getCompatActorJoins( $prefix ) {
+       protected function getNewActorJoins( $prefix ) {
                return [
                        "temp_{$prefix}_user" => [
-                               "LEFT JOIN",
+                               "JOIN",
                                "temp_{$prefix}_user.revactor_{$prefix} = {$prefix}_id",
                        ],
                        "actor_{$prefix}_user" => [
-                               "LEFT JOIN",
+                               "JOIN",
                                "actor_{$prefix}_user.actor_id = temp_{$prefix}_user.revactor_actor",
                        ],
                ];
@@ -163,7 +155,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_NEW,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [
                                'tables' => [
@@ -189,7 +181,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [
                                'tables' => [
@@ -199,13 +191,13 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                ],
                                'fields' => array_merge(
                                        $this->getArchiveQueryFields( false ),
-                                       $this->getCompatActorQueryFields( 'ar' ),
+                                       $this->getNewActorQueryFields( 'ar' ),
                                        $this->getCompatCommentQueryFields( 'ar' )
                                ),
                                'joins' => [
                                        'comment_ar_comment'
                                                => [ 'LEFT JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
-                                       'actor_ar_user' => [ 'LEFT JOIN', 'actor_ar_user.actor_id = ar_actor' ],
+                                       'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
                                ],
                        ]
                ];
@@ -215,24 +207,22 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [
                                'tables' => [
                                        'archive',
-                                       'actor_ar_user' => 'actor',
                                        'comment_ar_comment' => 'comment',
                                ],
                                'fields' => array_merge(
                                        $this->getArchiveQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'ar' ),
-                                       $this->getCompatActorQueryFields( 'ar' ),
+                                       $this->getOldActorQueryFields( 'ar' ),
                                        $this->getCompatCommentQueryFields( 'ar' )
                                ),
                                'joins' => [
                                        'comment_ar_comment'
                                                => [ 'LEFT JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
-                                       'actor_ar_user' => [ 'LEFT JOIN', 'actor_ar_user.actor_id = ar_actor' ],
                                ],
                        ]
                ];
@@ -241,7 +231,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [
                                'tables' => [
@@ -264,7 +254,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_NEW,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -309,7 +299,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -326,7 +316,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        $this->getRevisionQueryFields( false ),
                                        $this->getPageQueryFields(),
                                        $this->getUserQueryFields(),
-                                       $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getCompatCommentQueryFields( 'rev' )
                                ),
                                'joins' => array_merge(
@@ -335,12 +325,12 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                                'user' => [
                                                        'LEFT JOIN',
                                                        [
-                                                               'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
-                                                               'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+                                                               'actor_rev_user.actor_user != 0',
+                                                               'user_id = actor_rev_user.actor_user',
                                                        ]
                                                ],
                                        ],
-                                       $this->getCompatActorJoins( 'rev' ),
+                                       $this->getNewActorJoins( 'rev' ),
                                        $this->getCompatCommentJoins( 'rev' )
                                ),
                        ]
@@ -351,7 +341,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -368,7 +358,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        $this->getRevisionQueryFields( false ),
                                        $this->getPageQueryFields(),
                                        $this->getUserQueryFields(),
-                                       $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getCompatCommentQueryFields( 'rev' )
                                ),
                                'joins' => array_merge(
@@ -377,12 +367,12 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                                'user' => [
                                                        'LEFT JOIN',
                                                        [
-                                                               'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
-                                                               'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+                                                               'actor_rev_user.actor_user != 0',
+                                                               'user_id = actor_rev_user.actor_user'
                                                        ]
                                                ],
                                        ],
-                                       $this->getCompatActorJoins( 'rev' ),
+                                       $this->getNewActorJoins( 'rev' ),
                                        $this->getCompatCommentJoins( 'rev' )
                                ),
                        ]
@@ -393,25 +383,22 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [],
                        [
                                'tables' => [
                                        'revision',
-                                       'temp_rev_user' => 'revision_actor_temp',
                                        'temp_rev_comment' => 'revision_comment_temp',
-                                       'actor_rev_user' => 'actor',
                                        'comment_rev_comment' => 'comment',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'rev' ),
-                                       $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getCompatCommentQueryFields( 'rev' )
                                ),
                        'joins' => array_merge(
-                               $this->getCompatActorJoins( 'rev' ),
                                $this->getCompatCommentJoins( 'rev' )
                        ),
                        ]
@@ -422,7 +409,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -430,9 +417,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision',
                                        'page',
                                        'user',
-                                       'temp_rev_user' => 'revision_actor_temp',
                                        'temp_rev_comment' => 'revision_comment_temp',
-                                       'actor_rev_user' => 'actor',
                                        'comment_rev_comment' => 'comment',
                                ],
                                'fields' => array_merge(
@@ -440,7 +425,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        $this->getContentHandlerQueryFields( 'rev' ),
                                        $this->getUserQueryFields(),
                                        $this->getPageQueryFields(),
-                                       $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getCompatCommentQueryFields( 'rev' )
                                ),
                                'joins' => array_merge(
@@ -449,12 +434,11 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                                'user' => [
                                                        'LEFT JOIN',
                                                        [
-                                                               'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
-                                                               'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+                                                               'rev_user != 0',
+                                                               'user_id = rev_user'
                                                        ]
                                                ],
                                        ],
-                                       $this->getCompatActorJoins( 'rev' ),
                                        $this->getCompatCommentJoins( 'rev' )
                                ),
                        ]
@@ -464,7 +448,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [],
                        [
@@ -483,7 +467,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -507,7 +491,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [],
                        [
@@ -525,7 +509,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'page' ],
                        [
@@ -546,7 +530,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'user' ],
                        [
@@ -567,7 +551,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'text' ],
                        [
@@ -588,7 +572,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'text', 'page', 'user' ],
                        [
@@ -850,7 +834,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => true,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        'fields' => array_merge(
                                [
@@ -878,7 +862,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        'fields' => array_merge(
                                [
@@ -905,7 +889,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => true,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        'fields' => array_merge(
                                [
@@ -934,7 +918,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                               'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        'fields' => array_merge(
                                [
@@ -986,7 +970,7 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionUserJoinCond() {
                $this->hideDeprecated( 'Revision::userJoinCond' );
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
                $this->overrideMwServices();
                $this->assertEquals(
                        [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
index 355d2ce..0d6a439 100644 (file)
@@ -25,6 +25,7 @@ use TestUserRegistry;
 use Title;
 use WANObjectCache;
 use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\DatabaseSqlite;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\LoadBalancer;
@@ -78,7 +79,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                        'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
                        'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                       'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                ] );
 
                $this->overrideMwServices();
@@ -132,9 +133,13 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
         */
        private function getLoadBalancerMock( array $server ) {
+               $domain = new DatabaseDomain( $server['dbname'], null, $server['tablePrefix'] );
+
                $lb = $this->getMockBuilder( LoadBalancer::class )
                        ->setMethods( [ 'reallyOpenConnection' ] )
-                       ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
+                       ->setConstructorArgs( [
+                               [ 'servers' => [ $server ], 'localDomain' => $domain ]
+                       ] )
                        ->getMock();
 
                $lb->method( 'reallyOpenConnection' )->willReturnCallback(
index 4b44408..cc166a3 100644 (file)
@@ -91,7 +91,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
                        'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
-                       'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                ] );
 
                $this->overrideMwServices();
index 1ae27ff..5868b8d 100644 (file)
@@ -594,7 +594,7 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testLoadFromTitle() {
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
                $this->overrideMwServices();
                $title = $this->getMockTitle();
 
@@ -622,10 +622,11 @@ class RevisionTest extends MediaWikiTestCase {
                        'rev_content_model' => 'GOATMODEL',
                ];
 
+               $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
                $db = $this->getMock( IDatabase::class );
                $db->expects( $this->any() )
                        ->method( 'getDomainId' )
-                       ->will( $this->returnValue( wfWikiID() ) );
+                       ->will( $this->returnValue( $domain ) );
                $db->expects( $this->once() )
                        ->method( 'selectRow' )
                        ->with(
index f377993..ef7f2f5 100644 (file)
@@ -18,9 +18,15 @@ class NameTableStoreFactoryTest extends MediaWikiTestCase {
        /**
         * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
         */
-       private function getMockLoadBalancer() {
-               return $this->getMockBuilder( ILoadBalancer::class )
+       private function getMockLoadBalancer( $localDomain ) {
+               $mock = $this->getMockBuilder( ILoadBalancer::class )
                        ->disableOriginalConstructor()->getMock();
+
+               $mock->expects( $this->any() )
+                       ->method( 'getLocalDomainID' )
+                       ->willReturn( $localDomain );
+
+               return $mock;
        }
 
        /**
@@ -30,11 +36,16 @@ class NameTableStoreFactoryTest extends MediaWikiTestCase {
                $mock = $this->getMockBuilder( ILBFactory::class )
                        ->disableOriginalConstructor()->getMock();
 
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $localDomain = $lbFactory->getLocalDomainID();
+
+               $mock->expects( $this->any() )->method( 'getLocalDomainID' )->willReturn( $localDomain );
+
                $mock->expects( $this->once() )
                        ->method( 'getMainLB' )
                        ->with( $this->equalTo( $expectedWiki ) )
-                       ->willReturnCallback( function ( $domain ) use ( $expectedWiki ) {
-                               return $this->getMockLoadBalancer();
+                       ->willReturnCallback( function ( $domain ) use ( $localDomain ) {
+                               return $this->getMockLoadBalancer( $localDomain );
                        } );
 
                return $mock;
@@ -68,12 +79,9 @@ class NameTableStoreFactoryTest extends MediaWikiTestCase {
        /** @dataProvider provideTestGet */
        public function testGet( $tableName, $wiki, $expectedWiki ) {
                $services = MediaWikiServices::getInstance();
-               $db = wfGetDB( DB_MASTER );
-               if ( $wiki === false ) {
-                       $wiki2 = $db->getWikiID();
-               } else {
-                       $wiki2 = $wiki;
-               }
+               $wiki2 = ( $wiki === false )
+                       ? $services->getDBLoadBalancerFactory()->getLocalDomainID()
+                       : $wiki;
                $names = new NameTableStoreFactory(
                        $this->getMockLoadBalancerFactory( $expectedWiki ),
                        $services->getMainWANObjectCache(),
index ac86377..924a1a5 100644 (file)
@@ -15,7 +15,7 @@ class ApiQueryUserContribsTest extends ApiTestCase {
                        $wgActorTableSchemaMigrationStage = $v;
                        $this->overrideMwServices();
                }, [ $wgActorTableSchemaMigrationStage ] );
-               $wgActorTableSchemaMigrationStage = MIGRATION_WRITE_BOTH;
+               $wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD;
                $this->overrideMwServices();
 
                $users = [
@@ -44,19 +44,12 @@ class ApiQueryUserContribsTest extends ApiTestCase {
 
        /**
         * @dataProvider provideSorting
-        * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+        * @param int $stage SCHEMA_COMPAT contants for $wgActorTableSchemaMigrationStage
         * @param array $params Extra parameters for the query
         * @param bool $reverse Reverse order?
         * @param int $revs Number of revisions to expect
         */
        public function testSorting( $stage, $params, $reverse, $revs ) {
-               if ( isset( $params['ucuserprefix'] ) &&
-                       ( $stage === MIGRATION_WRITE_BOTH || $stage === MIGRATION_WRITE_NEW ) &&
-                       $this->db->getType() === 'mysql' && $this->usesTemporaryTables()
-               ) {
-                       // https://bugs.mysql.com/bug.php?id=10327
-                       $this->markTestSkipped( 'MySQL bug 10327 - can\'t reopen temporary tables' );
-               }
                // FIXME: fails under sqlite
                $this->markTestSkippedIfDbType( 'sqlite' );
 
@@ -127,10 +120,10 @@ class ApiQueryUserContribsTest extends ApiTestCase {
 
                foreach (
                        [
-                               'old' => MIGRATION_OLD,
-                               'write both' => MIGRATION_WRITE_BOTH,
-                               'write new' => MIGRATION_WRITE_NEW,
-                               'new' => MIGRATION_NEW,
+                               'old' => SCHEMA_COMPAT_OLD,
+                               'read old' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               'read new' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+                               'new' => SCHEMA_COMPAT_NEW,
                        ] as $stageName => $stage
                ) {
                        foreach ( [ false, true ] as $reverse ) {
@@ -152,7 +145,7 @@ class ApiQueryUserContribsTest extends ApiTestCase {
 
        /**
         * @dataProvider provideInterwikiUser
-        * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+        * @param int $stage SCHEMA_COMPAT constants for $wgActorTableSchemaMigrationStage
         */
        public function testInterwikiUser( $stage ) {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
@@ -186,10 +179,10 @@ class ApiQueryUserContribsTest extends ApiTestCase {
 
        public static function provideInterwikiUser() {
                return [
-                       'old' => [ MIGRATION_OLD ],
-                       'write both' => [ MIGRATION_WRITE_BOTH ],
-                       'write new' => [ MIGRATION_WRITE_NEW ],
-                       'new' => [ MIGRATION_NEW ],
+                       'old' => [ SCHEMA_COMPAT_OLD ],
+                       'read old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+                       'read new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
+                       'new' => [ SCHEMA_COMPAT_NEW ],
                ];
        }
 
index 0795609..936bee0 100644 (file)
@@ -57,7 +57,7 @@ class DatabaseTestHelper extends Database {
                        wfWarn( $msg );
                };
                $this->currentDomain = DatabaseDomain::newUnspecified();
-               $this->open( 'localhost', 'testuser', 'password', 'testdb' );
+               $this->open( 'localhost', 'testuser', 'password', 'testdb', null, '' );
        }
 
        /**
@@ -155,7 +155,7 @@ class DatabaseTestHelper extends Database {
                return 'test';
        }
 
-       function open( $server, $user, $password, $dbName ) {
+       function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
                $this->conn = (object)[ 'test' ];
 
                return true;
index 1616139..e84998c 100644 (file)
@@ -608,7 +608,15 @@ class LBFactoryTest extends MediaWikiTestCase {
                        $this->assertFalse( $db->isOpen() );
                } else {
                        \Wikimedia\suppressWarnings();
-                       $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+                       try {
+                               $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+                               $this->fail( "No error thrown." );
+                       } catch ( \Wikimedia\Rdbms\DBExpectedError $e ) {
+                               $this->assertEquals(
+                                       "Could not select database 'garbage-db'.",
+                                       $e->getMessage()
+                               );
+                       }
                        \Wikimedia\restoreWarnings();
                }
        }
index a86a1c9..b7dbe0b 100644 (file)
@@ -100,7 +100,7 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
        private function getMockForViews() {
                $db = $this->getMockBuilder( DatabaseMysqli::class )
                        ->disableOriginalConstructor()
-                       ->setMethods( [ 'fetchRow', 'query' ] )
+                       ->setMethods( [ 'fetchRow', 'query', 'getDBname' ] )
                        ->getMock();
 
                $db->method( 'query' )
@@ -110,6 +110,7 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
                                (object)[ 'Tables_in_' => 'view2' ],
                                (object)[ 'Tables_in_' => 'myview' ]
                        ] ) );
+               $db->method( 'getDBname' )->willReturn( '' );
 
                return $db;
        }
@@ -677,7 +678,7 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
        public function testIndexAliases() {
                $db = $this->getMockBuilder( DatabaseMysqli::class )
                        ->disableOriginalConstructor()
-                       ->setMethods( [ 'mysqlRealEscapeString' ] )
+                       ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
                        ->getMock();
                $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
                        function ( $s ) {
@@ -710,7 +711,7 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
        public function testTableAliases() {
                $db = $this->getMockBuilder( DatabaseMysqli::class )
                        ->disableOriginalConstructor()
-                       ->setMethods( [ 'mysqlRealEscapeString' ] )
+                       ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
                        ->getMock();
                $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
                        function ( $s ) {
index abde37a..762812c 100644 (file)
@@ -218,9 +218,10 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
         */
        public function testTransactionIdle_TRX() {
-               $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+               $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
                $db->method( 'isOpen' )->willReturn( true );
                $db->method( 'ping' )->willReturn( true );
+               $db->method( 'getDBname' )->willReturn( '' );
                $db->setFlag( DBO_TRX );
 
                $lbFactory = LBFactorySingle::newFromConnection( $db );
@@ -311,9 +312,10 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
         */
        public function testTransactionPreCommitOrIdle_TRX() {
-               $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+               $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
                $db->method( 'isOpen' )->willReturn( true );
                $db->method( 'ping' )->willReturn( true );
+               $db->method( 'getDBname' )->willReturn( 'unittest' );
                $db->setFlag( DBO_TRX );
 
                $lbFactory = LBFactorySingle::newFromConnection( $db );
@@ -484,8 +486,9 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\Database::lockIsFree
         */
        public function testGetScopedLock() {
-               $db = $this->getMockDB( [ 'isOpen' ] );
+               $db = $this->getMockDB( [ 'isOpen', 'getDBname' ] );
                $db->method( 'isOpen' )->willReturn( true );
+               $db->method( 'getDBname' )->willReturn( 'unittest' );
 
                $this->assertEquals( 0, $db->trxLevel() );
                $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
@@ -625,21 +628,57 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\Database::tablePrefix
         * @covers Wikimedia\Rdbms\Database::dbSchema
         */
-       public function testMutators() {
+       public function testSchemaAndPrefixMutators() {
                $old = $this->db->tablePrefix();
+               $oldDomain = $this->db->getDomainId();
                $this->assertInternalType( 'string', $old, 'Prefix is string' );
-               $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
-               $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
-               $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+               $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
+               $this->assertSame( $old, $this->db->tablePrefix( 'xxx' ) );
+               $this->assertSame( 'xxx', $this->db->tablePrefix(), "Prefix set" );
                $this->db->tablePrefix( $old );
                $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+               $this->assertSame( $oldDomain, $this->db->getDomainId() );
 
                $old = $this->db->dbSchema();
+               $oldDomain = $this->db->getDomainId();
                $this->assertInternalType( 'string', $old, 'Schema is string' );
-               $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
-               $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
-               $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
+               $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
+               $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
+               $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
                $this->db->dbSchema( $old );
                $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
+               $this->assertSame( $oldDomain, $this->db->getDomainId() );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::selectDomain
+        */
+       public function testSelectDomain() {
+               $oldDomain = $this->db->getDomainId();
+               $oldDatabase = $this->db->getDBname();
+               $oldSchema = $this->db->dbSchema();
+               $oldPrefix = $this->db->tablePrefix();
+
+               $this->db->selectDomain( 'testselectdb-xxx' );
+               $this->assertSame( 'testselectdb', $this->db->getDBname() );
+               $this->assertSame( '', $this->db->dbSchema() );
+               $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+               $this->db->selectDomain( $oldDomain );
+               $this->assertSame( $oldDatabase, $this->db->getDBname() );
+               $this->assertSame( $oldSchema, $this->db->dbSchema() );
+               $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+               $this->assertSame( $oldDomain, $this->db->getDomainId() );
+
+               $this->db->selectDomain( 'testselectdb-schema-xxx' );
+               $this->assertSame( 'testselectdb', $this->db->getDBname() );
+               $this->assertSame( 'schema', $this->db->dbSchema() );
+               $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+               $this->db->selectDomain( $oldDomain );
+               $this->assertSame( $oldDatabase, $this->db->getDBname() );
+               $this->assertSame( $oldSchema, $this->db->dbSchema() );
+               $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+               $this->assertSame( $oldDomain, $this->db->getDomainId() );
        }
 }
index 4af1742..e75b173 100644 (file)
@@ -29,17 +29,19 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
         * @param array $selectFields
         * @param string[]|null $row
         * @param string[]|null $expectedFields
-        * @param string $migration
+        * @param int $commentMigration
+        * @param int $actorMigration
         */
        public function testNewFromId( $id,
                array $selectFields,
                array $row = null,
                array $expectedFields = null,
-               $migration
+               $commentMigration,
+               $actorMigration
        ) {
                $this->setMwGlobals( [
-                       'wgCommentTableSchemaMigrationStage' => $migration,
-                       'wgActorTableSchemaMigrationStage' => $migration,
+                       'wgCommentTableSchemaMigrationStage' => $commentMigration,
+                       'wgActorTableSchemaMigrationStage' => $actorMigration,
                ] );
 
                $row = $row ? (object)$row : null;
@@ -132,6 +134,7 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
                                null,
                                null,
                                MIGRATION_OLD,
+                               SCHEMA_COMPAT_OLD,
                        ],
                        [
                                123,
@@ -144,6 +147,7 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
                                ],
                                [ 'type' => 'foobarize', 'comment' => 'test!' ],
                                MIGRATION_OLD,
+                               SCHEMA_COMPAT_OLD,
                        ],
                        [
                                567,
@@ -156,6 +160,7 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
                                ],
                                [ 'type' => 'foobarize', 'comment' => 'test!' ],
                                MIGRATION_NEW,
+                               SCHEMA_COMPAT_NEW,
                        ],
                ];
        }
index 6be1977..ade8efd 100644 (file)
@@ -81,7 +81,7 @@ abstract class PageArchiveTestBase extends MediaWikiTestCase {
                $this->tablesUsed += $this->getMcrTablesToReset();
 
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
                $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
                $this->setMwGlobals(
                        'wgMultiContentRevisionSchemaMigrationStage',
index 31e7a0a..d9c92f5 100644 (file)
@@ -808,6 +808,14 @@ abstract class WikiPageDbTestBase extends MediaWikiLangTestCase {
                                "#REDIRECT [[hello world]]",
                                "Hello world"
                        ],
+                       // The below added to protect against Media namespace
+                       // redirects which throw a fatal: (T203942)
+                       [
+                               'WikiPageTest_testGetRedirectTarget_3',
+                               CONTENT_MODEL_WIKITEXT,
+                               "#REDIRECT [[Media:hello_world]]",
+                               "File:Hello world"
+                       ],
                ];
        }
 
index 94cbf5c..390ea41 100644 (file)
@@ -149,37 +149,6 @@ class ParserOutputTest extends MediaWikiLangTestCase {
                $this->assertNotContains( 'class="foo bar"', $text );
        }
 
-       public function testT203716() {
-               // simulate extra wrapping from old parser cache
-               $out = new ParserOutput( '<div class="mw-parser-output">Foo</div>' );
-               $out = unserialize( serialize( $out ) );
-
-               $plainText = $out->getText( [ 'unwrap' => true ] );
-               $wrappedText = $out->getText( [ 'unwrap' => false ] );
-               $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
-               $this->assertNotContains( '<div', $plainText );
-               $this->assertContains( '<div', $wrappedText );
-               $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
-               $this->assertContains( '<div', $wrappedText2 );
-               $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
-
-               // simulate ParserOuput creation by new parser code
-               $out = new ParserOutput( 'Foo' );
-               $out->addWrapperDivClass( 'mw-parser-outout' );
-               $out = unserialize( serialize( $out ) );
-
-               $plainText = $out->getText( [ 'unwrap' => true ] );
-               $wrappedText = $out->getText( [ 'unwrap' => false ] );
-               $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
-               $this->assertNotContains( '<div', $plainText );
-               $this->assertContains( '<div', $wrappedText );
-               $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
-               $this->assertContains( '<div', $wrappedText2 );
-               $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
-       }
-
        /**
         * @covers ParserOutput::getText
         * @dataProvider provideGetText
index b874215..b8cee67 100644 (file)
@@ -197,15 +197,16 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testRcHidemyselfFilter() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "NOT((rc_actor = '{$user->getActorId()}') OR "
-                                       . "(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
+                               "NOT((rc_user = '{$user->getId()}'))",
                        ],
                        [
                                'hidemyself' => 1,
@@ -218,7 +219,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $id = $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
+                               "NOT((rc_user_text = '10.11.12.13'))",
                        ],
                        [
                                'hidemyself' => 1,
@@ -229,15 +230,16 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testRcHidebyothersFilter() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "(rc_actor = '{$user->getActorId()}') OR "
-                               . "(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
+                               "(rc_user_text = '{$user->getName()}')",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -250,7 +252,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $id = $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
+                               "(rc_user_text = '10.11.12.13')",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -462,13 +464,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelAllExperienceLevels() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $this->assertConditions(
                        [
                                # expected
-                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+                               'rc_user != 0',
                        ],
                        [
                                'userExpLevel' => 'newcomer;learner;experienced',
@@ -478,13 +482,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelRegistrered() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $this->assertConditions(
                        [
                                # expected
-                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+                               'rc_user != 0',
                        ],
                        [
                                'userExpLevel' => 'registered',
@@ -494,13 +500,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelUnregistrered() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $this->assertConditions(
                        [
                                # expected
-                               'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
+                               'rc_user = 0',
                        ],
                        [
                                'userExpLevel' => 'unregistered',
@@ -510,13 +518,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelRegistreredOrLearner() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $this->assertConditions(
                        [
                                # expected
-                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+                               'rc_user != 0',
                        ],
                        [
                                'userExpLevel' => 'registered;learner',
@@ -526,13 +536,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelUnregistreredOrExperienced() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->setMwGlobals(
+                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+               );
                $this->overrideMwServices();
 
                $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
 
                $this->assertRegExp(
-                       '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
+                       '/\(rc_user = 0\) OR '
                                . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
                        reset( $conds ),
                        "rc conditions: userExpLevel=unregistered;experienced"
index f86987a..cee15e8 100644 (file)
@@ -22,7 +22,7 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgGroupPermissions' => [],
                        'wgRevokePermissions' => [],
-                       'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                ] );
                $this->overrideMwServices();
 
index 8ccacfc..0ce0e90 100644 (file)
@@ -169,9 +169,8 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
                        $testString .= 'xxx xxx xxx';
                }
                $testString .= "\n<big id='в'></big>";
-               $old = ini_set( 'pcre.backtrack_limit', 200 );
+               $this->setIniSetting( 'pcre.backtrack_limit', 200 );
                $result = $this->lc->autoConvert( $testString, 'tg-latn' );
-               ini_set( 'pcre.backtrack_limit', $old );
                // The в in the id attribute should not get converted to a v
                $this->assertFalse(
                        strpos( $result, 'v' ),